Netease backend support

This commit is contained in:
LittleSheep 2024-09-04 23:28:59 +08:00
parent 010ee6286f
commit 19a7fd82df
19 changed files with 704 additions and 370 deletions

View File

@ -8,10 +8,10 @@ Their original app is good enough. But I just want to redesign the user interfac
## Roadmap ## Roadmap
- [x] Playing music - [x] Playing music
- [ ] Add netease music as source - [x] Add netease music as source
- [x] Re-design user interface - [x] Re-design user interface
- [x] Simplified UI and UX - [x] Simplified UI and UX
- [ ] Support for large screen device - [x] Support for large screen device
## License ## License

View File

@ -1,4 +1,5 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"> <manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.WAKE_LOCK" /> <uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />

View File

@ -7,7 +7,7 @@ class ErrorNotifier extends GetxController {
Rx<MaterialBanner?> showing = Rx(null); Rx<MaterialBanner?> showing = Rx(null);
void logError(String msg, {StackTrace? trace}) { void logError(String msg, {StackTrace? trace}) {
log('$msg${trace != null ? '\nTrace:\ntrace' : ''}'); log('$msg${trace != null ? '\nTrace:\n$trace' : ''}');
showError(msg); showError(msg);
} }

View File

@ -141,6 +141,10 @@ class UserPreferencesProvider extends GetxController {
setData(PreferencesTableCompanion(locale: Value(locale))); setData(PreferencesTableCompanion(locale: Value(locale)));
} }
void setNeteaseApiInstance(String instance) {
setData(PreferencesTableCompanion(neteaseApiInstance: Value(instance)));
}
void setPipedInstance(String instance) { void setPipedInstance(String instance) {
setData(PreferencesTableCompanion(pipedInstance: Value(instance))); setData(PreferencesTableCompanion(pipedInstance: Value(instance)));
} }
@ -161,10 +165,6 @@ class UserPreferencesProvider extends GetxController {
setData(PreferencesTableCompanion(systemTitleBar: Value(isSystemTitleBar))); setData(PreferencesTableCompanion(systemTitleBar: Value(isSystemTitleBar)));
} }
void setDiscordPresence(bool discordPresence) {
setData(PreferencesTableCompanion(discordPresence: Value(discordPresence)));
}
void setNormalizeAudio(bool normalize) { void setNormalizeAudio(bool normalize) {
setData(PreferencesTableCompanion(normalizeAudio: Value(normalize))); setData(PreferencesTableCompanion(normalizeAudio: Value(normalize)));
audioPlayer.setAudioNormalization(normalize); audioPlayer.setAudioNormalization(normalize);

View File

@ -71,6 +71,7 @@ class _ExploreScreenState extends State<ExploreScreen> {
return; return;
} }
if (_auth.auth.value != null) {
final customEndpoint = final customEndpoint =
CustomSpotifyEndpoints(_auth.auth.value?.accessToken.value ?? ''); CustomSpotifyEndpoints(_auth.auth.value?.accessToken.value ?? '');
final forYouView = await customEndpoint.getView( final forYouView = await customEndpoint.getView(
@ -79,6 +80,7 @@ class _ExploreScreenState extends State<ExploreScreen> {
locale: Intl.canonicalizedLocale(locale.toString()), locale: Intl.canonicalizedLocale(locale.toString()),
); );
_forYouView = forYouView['content']?['items']; _forYouView = forYouView['content']?['items'];
}
if (mounted) { if (mounted) {
setState(() => _isLoading['forYou'] = false); setState(() => _isLoading['forYou'] = false);
} else { } else {

View File

@ -69,9 +69,10 @@ class _PlayerScreenState extends State<PlayerScreen> {
Navigator.of(context).pop(); Navigator.of(context).pop();
}, },
direction: DismissiblePageDismissDirection.down, direction: DismissiblePageDismissDirection.down,
child: Material( child: Scaffold(
color: Colors.transparent, backgroundColor: Colors.transparent,
child: SafeArea( body: SafeArea(
child: Center(
child: Row( child: Row(
children: [ children: [
Expanded( Expanded(
@ -103,7 +104,8 @@ class _PlayerScreenState extends State<PlayerScreen> {
width: 64, width: 64,
height: 64, height: 64,
child: const Center( child: const Center(
child: Icon(Icons.image)), child: Icon(Icons.image),
),
), ),
), ),
).marginSymmetric(horizontal: 24), ).marginSymmetric(horizontal: 24),
@ -122,14 +124,16 @@ class _PlayerScreenState extends State<PlayerScreen> {
Text( Text(
_playback.state.value.activeTrack?.name ?? _playback.state.value.activeTrack?.name ??
'Not playing', 'Not playing',
style: Theme.of(context).textTheme.titleLarge, style:
Theme.of(context).textTheme.titleLarge,
textAlign: TextAlign.left, textAlign: TextAlign.left,
), ),
Text( Text(
_playback.state.value.activeTrack?.artists _playback.state.value.activeTrack?.artists
?.asString() ?? ?.asString() ??
'No author', 'No author',
style: Theme.of(context).textTheme.bodyMedium, style:
Theme.of(context).textTheme.bodyMedium,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
textAlign: TextAlign.left, textAlign: TextAlign.left,
), ),
@ -166,7 +170,8 @@ class _PlayerScreenState extends State<PlayerScreen> {
.abs() .abs()
.toDouble(), .toDouble(),
value: _draggingValue?.abs() ?? value: _draggingValue?.abs() ??
_playback.durationCurrent.value.inMilliseconds _playback
.durationCurrent.value.inMilliseconds
.toDouble() .toDouble()
.abs(), .abs(),
min: 0, min: 0,
@ -293,7 +298,8 @@ class _PlayerScreenState extends State<PlayerScreen> {
PlaylistMode.single, PlaylistMode.single,
PlaylistMode.single => PlaylistMode.single =>
PlaylistMode.none, PlaylistMode.none,
PlaylistMode.none => PlaylistMode.loop, PlaylistMode.none =>
PlaylistMode.loop,
}, },
); );
}, },
@ -313,7 +319,8 @@ class _PlayerScreenState extends State<PlayerScreen> {
useRootNavigator: true, useRootNavigator: true,
isScrollControlled: true, isScrollControlled: true,
context: context, context: context,
builder: (context) => const PlayerQueuePopup(), builder: (context) =>
const PlayerQueuePopup(),
).then((_) { ).then((_) {
if (mounted) { if (mounted) {
setState(() {}); setState(() {});
@ -329,7 +336,8 @@ class _PlayerScreenState extends State<PlayerScreen> {
icon: const Icon(Icons.lyrics), icon: const Icon(Icons.lyrics),
label: const Text('Lyrics'), label: const Text('Lyrics'),
onPressed: () { onPressed: () {
GoRouter.of(context).pushNamed('playerLyrics'); GoRouter.of(context)
.pushNamed('playerLyrics');
}, },
), ),
), ),
@ -365,6 +373,7 @@ class _PlayerScreenState extends State<PlayerScreen> {
) )
], ],
), ),
),
).marginSymmetric(horizontal: 24), ).marginSymmetric(horizontal: 24),
), ),
); );

View File

@ -1,3 +1,4 @@
import 'package:dropdown_button2/dropdown_button2.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
@ -5,6 +6,7 @@ import 'package:rhythm_box/providers/auth.dart';
import 'package:rhythm_box/providers/spotify.dart'; import 'package:rhythm_box/providers/spotify.dart';
import 'package:rhythm_box/providers/user_preferences.dart'; import 'package:rhythm_box/providers/user_preferences.dart';
import 'package:rhythm_box/screens/auth/login.dart'; import 'package:rhythm_box/screens/auth/login.dart';
import 'package:rhythm_box/services/database/database.dart';
import 'package:rhythm_box/widgets/auto_cache_image.dart'; import 'package:rhythm_box/widgets/auto_cache_image.dart';
import 'package:rhythm_box/widgets/sized_container.dart'; import 'package:rhythm_box/widgets/sized_container.dart';
@ -101,6 +103,53 @@ class _SettingsScreenState extends State<SettingsScreen> {
); );
}), }),
const Divider(thickness: 0.3, height: 1), const Divider(thickness: 0.3, height: 1),
Obx(
() => ListTile(
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
leading: const Icon(Icons.audio_file),
title: const Text('Audio Source'),
subtitle:
const Text('Choose who to provide the songs you played.'),
trailing: DropdownButtonHideUnderline(
child: DropdownButton2<AudioSource>(
isExpanded: true,
hint: Text(
'Select Item',
style: TextStyle(
fontSize: 14,
color: Theme.of(context).hintColor,
),
),
items: AudioSource.values
.map((AudioSource item) =>
DropdownMenuItem<AudioSource>(
value: item,
child: Text(
item.label,
style: const TextStyle(
fontSize: 14,
),
),
))
.toList(),
value: _preferences.state.value.audioSource,
onChanged: (AudioSource? value) {
_preferences
.setAudioSource(value ?? AudioSource.youtube);
},
buttonStyleData: const ButtonStyleData(
padding: EdgeInsets.symmetric(horizontal: 16),
height: 40,
width: 140,
),
menuItemStyleData: const MenuItemStyleData(
height: 40,
),
),
),
),
),
const Divider(thickness: 0.3, height: 1),
Obx( Obx(
() => SwitchListTile( () => SwitchListTile(
contentPadding: const EdgeInsets.symmetric(horizontal: 24), contentPadding: const EdgeInsets.symmetric(horizontal: 24),

View File

@ -94,7 +94,7 @@ abstract class AudioPlayerInterface {
), ),
) { ) {
_mkPlayer.stream.error.listen((event) { _mkPlayer.stream.error.listen((event) {
Get.find<ErrorNotifier>().logError('[Playback] Error: $event'); Get.find<ErrorNotifier>().logError('[Playback][Player] Error: $event');
}); });
} }

View File

@ -50,7 +50,8 @@ class CustomPlayer extends Player {
} }
}), }),
stream.error.listen((event) { stream.error.listen((event) {
Get.find<ErrorNotifier>().logError('[Playback] Error: $event'); Get.find<ErrorNotifier>()
.logError('[Playback][CustomLayer] Error: $event');
}), }),
]; ];
PackageInfo.fromPlatform().then((packageInfo) { PackageInfo.fromPlatform().then((packageInfo) {

View File

@ -453,6 +453,15 @@ class $PreferencesTableTable extends PreferencesTable
type: DriftSqlType.string, type: DriftSqlType.string,
requiredDuringInsert: false, requiredDuringInsert: false,
defaultValue: const Constant('https://pipedapi.kavin.rocks')); defaultValue: const Constant('https://pipedapi.kavin.rocks'));
static const VerificationMeta _neteaseApiInstanceMeta =
const VerificationMeta('neteaseApiInstance');
@override
late final GeneratedColumn<String> neteaseApiInstance =
GeneratedColumn<String>('netease_api_instance', aliasedName, false,
type: DriftSqlType.string,
requiredDuringInsert: false,
defaultValue:
const Constant('https://rhythmbox-netease-music-api.vercel.app'));
static const VerificationMeta _themeModeMeta = static const VerificationMeta _themeModeMeta =
const VerificationMeta('themeMode'); const VerificationMeta('themeMode');
@override @override
@ -494,16 +503,6 @@ class $PreferencesTableTable extends PreferencesTable
defaultValue: Constant(SourceCodecs.m4a.name)) defaultValue: Constant(SourceCodecs.m4a.name))
.withConverter<SourceCodecs>( .withConverter<SourceCodecs>(
$PreferencesTableTable.$converterdownloadMusicCodec); $PreferencesTableTable.$converterdownloadMusicCodec);
static const VerificationMeta _discordPresenceMeta =
const VerificationMeta('discordPresence');
@override
late final GeneratedColumn<bool> discordPresence = GeneratedColumn<bool>(
'discord_presence', aliasedName, false,
type: DriftSqlType.bool,
requiredDuringInsert: false,
defaultConstraints: GeneratedColumn.constraintIsAlways(
'CHECK ("discord_presence" IN (0, 1))'),
defaultValue: const Constant(true));
static const VerificationMeta _endlessPlaybackMeta = static const VerificationMeta _endlessPlaybackMeta =
const VerificationMeta('endlessPlayback'); const VerificationMeta('endlessPlayback');
@override @override
@ -543,11 +542,11 @@ class $PreferencesTableTable extends PreferencesTable
downloadLocation, downloadLocation,
localLibraryLocation, localLibraryLocation,
pipedInstance, pipedInstance,
neteaseApiInstance,
themeMode, themeMode,
audioSource, audioSource,
streamMusicCodec, streamMusicCodec,
downloadMusicCodec, downloadMusicCodec,
discordPresence,
endlessPlayback, endlessPlayback,
playerWakelock playerWakelock
]; ];
@ -622,16 +621,16 @@ class $PreferencesTableTable extends PreferencesTable
pipedInstance.isAcceptableOrUnknown( pipedInstance.isAcceptableOrUnknown(
data['piped_instance']!, _pipedInstanceMeta)); data['piped_instance']!, _pipedInstanceMeta));
} }
if (data.containsKey('netease_api_instance')) {
context.handle(
_neteaseApiInstanceMeta,
neteaseApiInstance.isAcceptableOrUnknown(
data['netease_api_instance']!, _neteaseApiInstanceMeta));
}
context.handle(_themeModeMeta, const VerificationResult.success()); context.handle(_themeModeMeta, const VerificationResult.success());
context.handle(_audioSourceMeta, const VerificationResult.success()); context.handle(_audioSourceMeta, const VerificationResult.success());
context.handle(_streamMusicCodecMeta, const VerificationResult.success()); context.handle(_streamMusicCodecMeta, const VerificationResult.success());
context.handle(_downloadMusicCodecMeta, const VerificationResult.success()); context.handle(_downloadMusicCodecMeta, const VerificationResult.success());
if (data.containsKey('discord_presence')) {
context.handle(
_discordPresenceMeta,
discordPresence.isAcceptableOrUnknown(
data['discord_presence']!, _discordPresenceMeta));
}
if (data.containsKey('endless_playback')) { if (data.containsKey('endless_playback')) {
context.handle( context.handle(
_endlessPlaybackMeta, _endlessPlaybackMeta,
@ -696,6 +695,8 @@ class $PreferencesTableTable extends PreferencesTable
data['${effectivePrefix}local_library_location'])!), data['${effectivePrefix}local_library_location'])!),
pipedInstance: attachedDatabase.typeMapping pipedInstance: attachedDatabase.typeMapping
.read(DriftSqlType.string, data['${effectivePrefix}piped_instance'])!, .read(DriftSqlType.string, data['${effectivePrefix}piped_instance'])!,
neteaseApiInstance: attachedDatabase.typeMapping.read(
DriftSqlType.string, data['${effectivePrefix}netease_api_instance'])!,
themeMode: $PreferencesTableTable.$converterthemeMode.fromSql( themeMode: $PreferencesTableTable.$converterthemeMode.fromSql(
attachedDatabase.typeMapping.read( attachedDatabase.typeMapping.read(
DriftSqlType.string, data['${effectivePrefix}theme_mode'])!), DriftSqlType.string, data['${effectivePrefix}theme_mode'])!),
@ -708,8 +709,6 @@ class $PreferencesTableTable extends PreferencesTable
downloadMusicCodec: $PreferencesTableTable.$converterdownloadMusicCodec downloadMusicCodec: $PreferencesTableTable.$converterdownloadMusicCodec
.fromSql(attachedDatabase.typeMapping.read(DriftSqlType.string, .fromSql(attachedDatabase.typeMapping.read(DriftSqlType.string,
data['${effectivePrefix}download_music_codec'])!), data['${effectivePrefix}download_music_codec'])!),
discordPresence: attachedDatabase.typeMapping
.read(DriftSqlType.bool, data['${effectivePrefix}discord_presence'])!,
endlessPlayback: attachedDatabase.typeMapping endlessPlayback: attachedDatabase.typeMapping
.read(DriftSqlType.bool, data['${effectivePrefix}endless_playback'])!, .read(DriftSqlType.bool, data['${effectivePrefix}endless_playback'])!,
playerWakelock: attachedDatabase.typeMapping playerWakelock: attachedDatabase.typeMapping
@ -771,11 +770,11 @@ class PreferencesTableData extends DataClass
final String downloadLocation; final String downloadLocation;
final List<String> localLibraryLocation; final List<String> localLibraryLocation;
final String pipedInstance; final String pipedInstance;
final String neteaseApiInstance;
final ThemeMode themeMode; final ThemeMode themeMode;
final AudioSource audioSource; final AudioSource audioSource;
final SourceCodecs streamMusicCodec; final SourceCodecs streamMusicCodec;
final SourceCodecs downloadMusicCodec; final SourceCodecs downloadMusicCodec;
final bool discordPresence;
final bool endlessPlayback; final bool endlessPlayback;
final bool playerWakelock; final bool playerWakelock;
const PreferencesTableData( const PreferencesTableData(
@ -796,11 +795,11 @@ class PreferencesTableData extends DataClass
required this.downloadLocation, required this.downloadLocation,
required this.localLibraryLocation, required this.localLibraryLocation,
required this.pipedInstance, required this.pipedInstance,
required this.neteaseApiInstance,
required this.themeMode, required this.themeMode,
required this.audioSource, required this.audioSource,
required this.streamMusicCodec, required this.streamMusicCodec,
required this.downloadMusicCodec, required this.downloadMusicCodec,
required this.discordPresence,
required this.endlessPlayback, required this.endlessPlayback,
required this.playerWakelock}); required this.playerWakelock});
@override @override
@ -849,6 +848,7 @@ class PreferencesTableData extends DataClass
.toSql(localLibraryLocation)); .toSql(localLibraryLocation));
} }
map['piped_instance'] = Variable<String>(pipedInstance); map['piped_instance'] = Variable<String>(pipedInstance);
map['netease_api_instance'] = Variable<String>(neteaseApiInstance);
{ {
map['theme_mode'] = Variable<String>( map['theme_mode'] = Variable<String>(
$PreferencesTableTable.$converterthemeMode.toSql(themeMode)); $PreferencesTableTable.$converterthemeMode.toSql(themeMode));
@ -867,7 +867,6 @@ class PreferencesTableData extends DataClass
.$converterdownloadMusicCodec .$converterdownloadMusicCodec
.toSql(downloadMusicCodec)); .toSql(downloadMusicCodec));
} }
map['discord_presence'] = Variable<bool>(discordPresence);
map['endless_playback'] = Variable<bool>(endlessPlayback); map['endless_playback'] = Variable<bool>(endlessPlayback);
map['player_wakelock'] = Variable<bool>(playerWakelock); map['player_wakelock'] = Variable<bool>(playerWakelock);
return map; return map;
@ -892,11 +891,11 @@ class PreferencesTableData extends DataClass
downloadLocation: Value(downloadLocation), downloadLocation: Value(downloadLocation),
localLibraryLocation: Value(localLibraryLocation), localLibraryLocation: Value(localLibraryLocation),
pipedInstance: Value(pipedInstance), pipedInstance: Value(pipedInstance),
neteaseApiInstance: Value(neteaseApiInstance),
themeMode: Value(themeMode), themeMode: Value(themeMode),
audioSource: Value(audioSource), audioSource: Value(audioSource),
streamMusicCodec: Value(streamMusicCodec), streamMusicCodec: Value(streamMusicCodec),
downloadMusicCodec: Value(downloadMusicCodec), downloadMusicCodec: Value(downloadMusicCodec),
discordPresence: Value(discordPresence),
endlessPlayback: Value(endlessPlayback), endlessPlayback: Value(endlessPlayback),
playerWakelock: Value(playerWakelock), playerWakelock: Value(playerWakelock),
); );
@ -930,6 +929,8 @@ class PreferencesTableData extends DataClass
localLibraryLocation: localLibraryLocation:
serializer.fromJson<List<String>>(json['localLibraryLocation']), serializer.fromJson<List<String>>(json['localLibraryLocation']),
pipedInstance: serializer.fromJson<String>(json['pipedInstance']), pipedInstance: serializer.fromJson<String>(json['pipedInstance']),
neteaseApiInstance:
serializer.fromJson<String>(json['neteaseApiInstance']),
themeMode: $PreferencesTableTable.$converterthemeMode themeMode: $PreferencesTableTable.$converterthemeMode
.fromJson(serializer.fromJson<String>(json['themeMode'])), .fromJson(serializer.fromJson<String>(json['themeMode'])),
audioSource: $PreferencesTableTable.$converteraudioSource audioSource: $PreferencesTableTable.$converteraudioSource
@ -938,7 +939,6 @@ class PreferencesTableData extends DataClass
.fromJson(serializer.fromJson<String>(json['streamMusicCodec'])), .fromJson(serializer.fromJson<String>(json['streamMusicCodec'])),
downloadMusicCodec: $PreferencesTableTable.$converterdownloadMusicCodec downloadMusicCodec: $PreferencesTableTable.$converterdownloadMusicCodec
.fromJson(serializer.fromJson<String>(json['downloadMusicCodec'])), .fromJson(serializer.fromJson<String>(json['downloadMusicCodec'])),
discordPresence: serializer.fromJson<bool>(json['discordPresence']),
endlessPlayback: serializer.fromJson<bool>(json['endlessPlayback']), endlessPlayback: serializer.fromJson<bool>(json['endlessPlayback']),
playerWakelock: serializer.fromJson<bool>(json['playerWakelock']), playerWakelock: serializer.fromJson<bool>(json['playerWakelock']),
); );
@ -970,6 +970,7 @@ class PreferencesTableData extends DataClass
'localLibraryLocation': 'localLibraryLocation':
serializer.toJson<List<String>>(localLibraryLocation), serializer.toJson<List<String>>(localLibraryLocation),
'pipedInstance': serializer.toJson<String>(pipedInstance), 'pipedInstance': serializer.toJson<String>(pipedInstance),
'neteaseApiInstance': serializer.toJson<String>(neteaseApiInstance),
'themeMode': serializer.toJson<String>( 'themeMode': serializer.toJson<String>(
$PreferencesTableTable.$converterthemeMode.toJson(themeMode)), $PreferencesTableTable.$converterthemeMode.toJson(themeMode)),
'audioSource': serializer.toJson<String>( 'audioSource': serializer.toJson<String>(
@ -980,7 +981,6 @@ class PreferencesTableData extends DataClass
'downloadMusicCodec': serializer.toJson<String>($PreferencesTableTable 'downloadMusicCodec': serializer.toJson<String>($PreferencesTableTable
.$converterdownloadMusicCodec .$converterdownloadMusicCodec
.toJson(downloadMusicCodec)), .toJson(downloadMusicCodec)),
'discordPresence': serializer.toJson<bool>(discordPresence),
'endlessPlayback': serializer.toJson<bool>(endlessPlayback), 'endlessPlayback': serializer.toJson<bool>(endlessPlayback),
'playerWakelock': serializer.toJson<bool>(playerWakelock), 'playerWakelock': serializer.toJson<bool>(playerWakelock),
}; };
@ -1004,11 +1004,11 @@ class PreferencesTableData extends DataClass
String? downloadLocation, String? downloadLocation,
List<String>? localLibraryLocation, List<String>? localLibraryLocation,
String? pipedInstance, String? pipedInstance,
String? neteaseApiInstance,
ThemeMode? themeMode, ThemeMode? themeMode,
AudioSource? audioSource, AudioSource? audioSource,
SourceCodecs? streamMusicCodec, SourceCodecs? streamMusicCodec,
SourceCodecs? downloadMusicCodec, SourceCodecs? downloadMusicCodec,
bool? discordPresence,
bool? endlessPlayback, bool? endlessPlayback,
bool? playerWakelock}) => bool? playerWakelock}) =>
PreferencesTableData( PreferencesTableData(
@ -1029,11 +1029,11 @@ class PreferencesTableData extends DataClass
downloadLocation: downloadLocation ?? this.downloadLocation, downloadLocation: downloadLocation ?? this.downloadLocation,
localLibraryLocation: localLibraryLocation ?? this.localLibraryLocation, localLibraryLocation: localLibraryLocation ?? this.localLibraryLocation,
pipedInstance: pipedInstance ?? this.pipedInstance, pipedInstance: pipedInstance ?? this.pipedInstance,
neteaseApiInstance: neteaseApiInstance ?? this.neteaseApiInstance,
themeMode: themeMode ?? this.themeMode, themeMode: themeMode ?? this.themeMode,
audioSource: audioSource ?? this.audioSource, audioSource: audioSource ?? this.audioSource,
streamMusicCodec: streamMusicCodec ?? this.streamMusicCodec, streamMusicCodec: streamMusicCodec ?? this.streamMusicCodec,
downloadMusicCodec: downloadMusicCodec ?? this.downloadMusicCodec, downloadMusicCodec: downloadMusicCodec ?? this.downloadMusicCodec,
discordPresence: discordPresence ?? this.discordPresence,
endlessPlayback: endlessPlayback ?? this.endlessPlayback, endlessPlayback: endlessPlayback ?? this.endlessPlayback,
playerWakelock: playerWakelock ?? this.playerWakelock, playerWakelock: playerWakelock ?? this.playerWakelock,
); );
@ -1081,6 +1081,9 @@ class PreferencesTableData extends DataClass
pipedInstance: data.pipedInstance.present pipedInstance: data.pipedInstance.present
? data.pipedInstance.value ? data.pipedInstance.value
: this.pipedInstance, : this.pipedInstance,
neteaseApiInstance: data.neteaseApiInstance.present
? data.neteaseApiInstance.value
: this.neteaseApiInstance,
themeMode: data.themeMode.present ? data.themeMode.value : this.themeMode, themeMode: data.themeMode.present ? data.themeMode.value : this.themeMode,
audioSource: audioSource:
data.audioSource.present ? data.audioSource.value : this.audioSource, data.audioSource.present ? data.audioSource.value : this.audioSource,
@ -1090,9 +1093,6 @@ class PreferencesTableData extends DataClass
downloadMusicCodec: data.downloadMusicCodec.present downloadMusicCodec: data.downloadMusicCodec.present
? data.downloadMusicCodec.value ? data.downloadMusicCodec.value
: this.downloadMusicCodec, : this.downloadMusicCodec,
discordPresence: data.discordPresence.present
? data.discordPresence.value
: this.discordPresence,
endlessPlayback: data.endlessPlayback.present endlessPlayback: data.endlessPlayback.present
? data.endlessPlayback.value ? data.endlessPlayback.value
: this.endlessPlayback, : this.endlessPlayback,
@ -1122,11 +1122,11 @@ class PreferencesTableData extends DataClass
..write('downloadLocation: $downloadLocation, ') ..write('downloadLocation: $downloadLocation, ')
..write('localLibraryLocation: $localLibraryLocation, ') ..write('localLibraryLocation: $localLibraryLocation, ')
..write('pipedInstance: $pipedInstance, ') ..write('pipedInstance: $pipedInstance, ')
..write('neteaseApiInstance: $neteaseApiInstance, ')
..write('themeMode: $themeMode, ') ..write('themeMode: $themeMode, ')
..write('audioSource: $audioSource, ') ..write('audioSource: $audioSource, ')
..write('streamMusicCodec: $streamMusicCodec, ') ..write('streamMusicCodec: $streamMusicCodec, ')
..write('downloadMusicCodec: $downloadMusicCodec, ') ..write('downloadMusicCodec: $downloadMusicCodec, ')
..write('discordPresence: $discordPresence, ')
..write('endlessPlayback: $endlessPlayback, ') ..write('endlessPlayback: $endlessPlayback, ')
..write('playerWakelock: $playerWakelock') ..write('playerWakelock: $playerWakelock')
..write(')')) ..write(')'))
@ -1152,11 +1152,11 @@ class PreferencesTableData extends DataClass
downloadLocation, downloadLocation,
localLibraryLocation, localLibraryLocation,
pipedInstance, pipedInstance,
neteaseApiInstance,
themeMode, themeMode,
audioSource, audioSource,
streamMusicCodec, streamMusicCodec,
downloadMusicCodec, downloadMusicCodec,
discordPresence,
endlessPlayback, endlessPlayback,
playerWakelock playerWakelock
]); ]);
@ -1181,11 +1181,11 @@ class PreferencesTableData extends DataClass
other.downloadLocation == this.downloadLocation && other.downloadLocation == this.downloadLocation &&
other.localLibraryLocation == this.localLibraryLocation && other.localLibraryLocation == this.localLibraryLocation &&
other.pipedInstance == this.pipedInstance && other.pipedInstance == this.pipedInstance &&
other.neteaseApiInstance == this.neteaseApiInstance &&
other.themeMode == this.themeMode && other.themeMode == this.themeMode &&
other.audioSource == this.audioSource && other.audioSource == this.audioSource &&
other.streamMusicCodec == this.streamMusicCodec && other.streamMusicCodec == this.streamMusicCodec &&
other.downloadMusicCodec == this.downloadMusicCodec && other.downloadMusicCodec == this.downloadMusicCodec &&
other.discordPresence == this.discordPresence &&
other.endlessPlayback == this.endlessPlayback && other.endlessPlayback == this.endlessPlayback &&
other.playerWakelock == this.playerWakelock); other.playerWakelock == this.playerWakelock);
} }
@ -1208,11 +1208,11 @@ class PreferencesTableCompanion extends UpdateCompanion<PreferencesTableData> {
final Value<String> downloadLocation; final Value<String> downloadLocation;
final Value<List<String>> localLibraryLocation; final Value<List<String>> localLibraryLocation;
final Value<String> pipedInstance; final Value<String> pipedInstance;
final Value<String> neteaseApiInstance;
final Value<ThemeMode> themeMode; final Value<ThemeMode> themeMode;
final Value<AudioSource> audioSource; final Value<AudioSource> audioSource;
final Value<SourceCodecs> streamMusicCodec; final Value<SourceCodecs> streamMusicCodec;
final Value<SourceCodecs> downloadMusicCodec; final Value<SourceCodecs> downloadMusicCodec;
final Value<bool> discordPresence;
final Value<bool> endlessPlayback; final Value<bool> endlessPlayback;
final Value<bool> playerWakelock; final Value<bool> playerWakelock;
const PreferencesTableCompanion({ const PreferencesTableCompanion({
@ -1233,11 +1233,11 @@ class PreferencesTableCompanion extends UpdateCompanion<PreferencesTableData> {
this.downloadLocation = const Value.absent(), this.downloadLocation = const Value.absent(),
this.localLibraryLocation = const Value.absent(), this.localLibraryLocation = const Value.absent(),
this.pipedInstance = const Value.absent(), this.pipedInstance = const Value.absent(),
this.neteaseApiInstance = const Value.absent(),
this.themeMode = const Value.absent(), this.themeMode = const Value.absent(),
this.audioSource = const Value.absent(), this.audioSource = const Value.absent(),
this.streamMusicCodec = const Value.absent(), this.streamMusicCodec = const Value.absent(),
this.downloadMusicCodec = const Value.absent(), this.downloadMusicCodec = const Value.absent(),
this.discordPresence = const Value.absent(),
this.endlessPlayback = const Value.absent(), this.endlessPlayback = const Value.absent(),
this.playerWakelock = const Value.absent(), this.playerWakelock = const Value.absent(),
}); });
@ -1259,11 +1259,11 @@ class PreferencesTableCompanion extends UpdateCompanion<PreferencesTableData> {
this.downloadLocation = const Value.absent(), this.downloadLocation = const Value.absent(),
this.localLibraryLocation = const Value.absent(), this.localLibraryLocation = const Value.absent(),
this.pipedInstance = const Value.absent(), this.pipedInstance = const Value.absent(),
this.neteaseApiInstance = const Value.absent(),
this.themeMode = const Value.absent(), this.themeMode = const Value.absent(),
this.audioSource = const Value.absent(), this.audioSource = const Value.absent(),
this.streamMusicCodec = const Value.absent(), this.streamMusicCodec = const Value.absent(),
this.downloadMusicCodec = const Value.absent(), this.downloadMusicCodec = const Value.absent(),
this.discordPresence = const Value.absent(),
this.endlessPlayback = const Value.absent(), this.endlessPlayback = const Value.absent(),
this.playerWakelock = const Value.absent(), this.playerWakelock = const Value.absent(),
}); });
@ -1285,11 +1285,11 @@ class PreferencesTableCompanion extends UpdateCompanion<PreferencesTableData> {
Expression<String>? downloadLocation, Expression<String>? downloadLocation,
Expression<String>? localLibraryLocation, Expression<String>? localLibraryLocation,
Expression<String>? pipedInstance, Expression<String>? pipedInstance,
Expression<String>? neteaseApiInstance,
Expression<String>? themeMode, Expression<String>? themeMode,
Expression<String>? audioSource, Expression<String>? audioSource,
Expression<String>? streamMusicCodec, Expression<String>? streamMusicCodec,
Expression<String>? downloadMusicCodec, Expression<String>? downloadMusicCodec,
Expression<bool>? discordPresence,
Expression<bool>? endlessPlayback, Expression<bool>? endlessPlayback,
Expression<bool>? playerWakelock, Expression<bool>? playerWakelock,
}) { }) {
@ -1313,12 +1313,13 @@ class PreferencesTableCompanion extends UpdateCompanion<PreferencesTableData> {
if (localLibraryLocation != null) if (localLibraryLocation != null)
'local_library_location': localLibraryLocation, 'local_library_location': localLibraryLocation,
if (pipedInstance != null) 'piped_instance': pipedInstance, if (pipedInstance != null) 'piped_instance': pipedInstance,
if (neteaseApiInstance != null)
'netease_api_instance': neteaseApiInstance,
if (themeMode != null) 'theme_mode': themeMode, if (themeMode != null) 'theme_mode': themeMode,
if (audioSource != null) 'audio_source': audioSource, if (audioSource != null) 'audio_source': audioSource,
if (streamMusicCodec != null) 'stream_music_codec': streamMusicCodec, if (streamMusicCodec != null) 'stream_music_codec': streamMusicCodec,
if (downloadMusicCodec != null) if (downloadMusicCodec != null)
'download_music_codec': downloadMusicCodec, 'download_music_codec': downloadMusicCodec,
if (discordPresence != null) 'discord_presence': discordPresence,
if (endlessPlayback != null) 'endless_playback': endlessPlayback, if (endlessPlayback != null) 'endless_playback': endlessPlayback,
if (playerWakelock != null) 'player_wakelock': playerWakelock, if (playerWakelock != null) 'player_wakelock': playerWakelock,
}); });
@ -1342,11 +1343,11 @@ class PreferencesTableCompanion extends UpdateCompanion<PreferencesTableData> {
Value<String>? downloadLocation, Value<String>? downloadLocation,
Value<List<String>>? localLibraryLocation, Value<List<String>>? localLibraryLocation,
Value<String>? pipedInstance, Value<String>? pipedInstance,
Value<String>? neteaseApiInstance,
Value<ThemeMode>? themeMode, Value<ThemeMode>? themeMode,
Value<AudioSource>? audioSource, Value<AudioSource>? audioSource,
Value<SourceCodecs>? streamMusicCodec, Value<SourceCodecs>? streamMusicCodec,
Value<SourceCodecs>? downloadMusicCodec, Value<SourceCodecs>? downloadMusicCodec,
Value<bool>? discordPresence,
Value<bool>? endlessPlayback, Value<bool>? endlessPlayback,
Value<bool>? playerWakelock}) { Value<bool>? playerWakelock}) {
return PreferencesTableCompanion( return PreferencesTableCompanion(
@ -1367,11 +1368,11 @@ class PreferencesTableCompanion extends UpdateCompanion<PreferencesTableData> {
downloadLocation: downloadLocation ?? this.downloadLocation, downloadLocation: downloadLocation ?? this.downloadLocation,
localLibraryLocation: localLibraryLocation ?? this.localLibraryLocation, localLibraryLocation: localLibraryLocation ?? this.localLibraryLocation,
pipedInstance: pipedInstance ?? this.pipedInstance, pipedInstance: pipedInstance ?? this.pipedInstance,
neteaseApiInstance: neteaseApiInstance ?? this.neteaseApiInstance,
themeMode: themeMode ?? this.themeMode, themeMode: themeMode ?? this.themeMode,
audioSource: audioSource ?? this.audioSource, audioSource: audioSource ?? this.audioSource,
streamMusicCodec: streamMusicCodec ?? this.streamMusicCodec, streamMusicCodec: streamMusicCodec ?? this.streamMusicCodec,
downloadMusicCodec: downloadMusicCodec ?? this.downloadMusicCodec, downloadMusicCodec: downloadMusicCodec ?? this.downloadMusicCodec,
discordPresence: discordPresence ?? this.discordPresence,
endlessPlayback: endlessPlayback ?? this.endlessPlayback, endlessPlayback: endlessPlayback ?? this.endlessPlayback,
playerWakelock: playerWakelock ?? this.playerWakelock, playerWakelock: playerWakelock ?? this.playerWakelock,
); );
@ -1443,6 +1444,9 @@ class PreferencesTableCompanion extends UpdateCompanion<PreferencesTableData> {
if (pipedInstance.present) { if (pipedInstance.present) {
map['piped_instance'] = Variable<String>(pipedInstance.value); map['piped_instance'] = Variable<String>(pipedInstance.value);
} }
if (neteaseApiInstance.present) {
map['netease_api_instance'] = Variable<String>(neteaseApiInstance.value);
}
if (themeMode.present) { if (themeMode.present) {
map['theme_mode'] = Variable<String>( map['theme_mode'] = Variable<String>(
$PreferencesTableTable.$converterthemeMode.toSql(themeMode.value)); $PreferencesTableTable.$converterthemeMode.toSql(themeMode.value));
@ -1462,9 +1466,6 @@ class PreferencesTableCompanion extends UpdateCompanion<PreferencesTableData> {
.$converterdownloadMusicCodec .$converterdownloadMusicCodec
.toSql(downloadMusicCodec.value)); .toSql(downloadMusicCodec.value));
} }
if (discordPresence.present) {
map['discord_presence'] = Variable<bool>(discordPresence.value);
}
if (endlessPlayback.present) { if (endlessPlayback.present) {
map['endless_playback'] = Variable<bool>(endlessPlayback.value); map['endless_playback'] = Variable<bool>(endlessPlayback.value);
} }
@ -1494,11 +1495,11 @@ class PreferencesTableCompanion extends UpdateCompanion<PreferencesTableData> {
..write('downloadLocation: $downloadLocation, ') ..write('downloadLocation: $downloadLocation, ')
..write('localLibraryLocation: $localLibraryLocation, ') ..write('localLibraryLocation: $localLibraryLocation, ')
..write('pipedInstance: $pipedInstance, ') ..write('pipedInstance: $pipedInstance, ')
..write('neteaseApiInstance: $neteaseApiInstance, ')
..write('themeMode: $themeMode, ') ..write('themeMode: $themeMode, ')
..write('audioSource: $audioSource, ') ..write('audioSource: $audioSource, ')
..write('streamMusicCodec: $streamMusicCodec, ') ..write('streamMusicCodec: $streamMusicCodec, ')
..write('downloadMusicCodec: $downloadMusicCodec, ') ..write('downloadMusicCodec: $downloadMusicCodec, ')
..write('discordPresence: $discordPresence, ')
..write('endlessPlayback: $endlessPlayback, ') ..write('endlessPlayback: $endlessPlayback, ')
..write('playerWakelock: $playerWakelock') ..write('playerWakelock: $playerWakelock')
..write(')')) ..write(')'))
@ -3950,11 +3951,11 @@ typedef $$PreferencesTableTableCreateCompanionBuilder
Value<String> downloadLocation, Value<String> downloadLocation,
Value<List<String>> localLibraryLocation, Value<List<String>> localLibraryLocation,
Value<String> pipedInstance, Value<String> pipedInstance,
Value<String> neteaseApiInstance,
Value<ThemeMode> themeMode, Value<ThemeMode> themeMode,
Value<AudioSource> audioSource, Value<AudioSource> audioSource,
Value<SourceCodecs> streamMusicCodec, Value<SourceCodecs> streamMusicCodec,
Value<SourceCodecs> downloadMusicCodec, Value<SourceCodecs> downloadMusicCodec,
Value<bool> discordPresence,
Value<bool> endlessPlayback, Value<bool> endlessPlayback,
Value<bool> playerWakelock, Value<bool> playerWakelock,
}); });
@ -3977,11 +3978,11 @@ typedef $$PreferencesTableTableUpdateCompanionBuilder
Value<String> downloadLocation, Value<String> downloadLocation,
Value<List<String>> localLibraryLocation, Value<List<String>> localLibraryLocation,
Value<String> pipedInstance, Value<String> pipedInstance,
Value<String> neteaseApiInstance,
Value<ThemeMode> themeMode, Value<ThemeMode> themeMode,
Value<AudioSource> audioSource, Value<AudioSource> audioSource,
Value<SourceCodecs> streamMusicCodec, Value<SourceCodecs> streamMusicCodec,
Value<SourceCodecs> downloadMusicCodec, Value<SourceCodecs> downloadMusicCodec,
Value<bool> discordPresence,
Value<bool> endlessPlayback, Value<bool> endlessPlayback,
Value<bool> playerWakelock, Value<bool> playerWakelock,
}); });
@ -4021,11 +4022,11 @@ class $$PreferencesTableTableTableManager extends RootTableManager<
Value<String> downloadLocation = const Value.absent(), Value<String> downloadLocation = const Value.absent(),
Value<List<String>> localLibraryLocation = const Value.absent(), Value<List<String>> localLibraryLocation = const Value.absent(),
Value<String> pipedInstance = const Value.absent(), Value<String> pipedInstance = const Value.absent(),
Value<String> neteaseApiInstance = const Value.absent(),
Value<ThemeMode> themeMode = const Value.absent(), Value<ThemeMode> themeMode = const Value.absent(),
Value<AudioSource> audioSource = const Value.absent(), Value<AudioSource> audioSource = const Value.absent(),
Value<SourceCodecs> streamMusicCodec = const Value.absent(), Value<SourceCodecs> streamMusicCodec = const Value.absent(),
Value<SourceCodecs> downloadMusicCodec = const Value.absent(), Value<SourceCodecs> downloadMusicCodec = const Value.absent(),
Value<bool> discordPresence = const Value.absent(),
Value<bool> endlessPlayback = const Value.absent(), Value<bool> endlessPlayback = const Value.absent(),
Value<bool> playerWakelock = const Value.absent(), Value<bool> playerWakelock = const Value.absent(),
}) => }) =>
@ -4047,11 +4048,11 @@ class $$PreferencesTableTableTableManager extends RootTableManager<
downloadLocation: downloadLocation, downloadLocation: downloadLocation,
localLibraryLocation: localLibraryLocation, localLibraryLocation: localLibraryLocation,
pipedInstance: pipedInstance, pipedInstance: pipedInstance,
neteaseApiInstance: neteaseApiInstance,
themeMode: themeMode, themeMode: themeMode,
audioSource: audioSource, audioSource: audioSource,
streamMusicCodec: streamMusicCodec, streamMusicCodec: streamMusicCodec,
downloadMusicCodec: downloadMusicCodec, downloadMusicCodec: downloadMusicCodec,
discordPresence: discordPresence,
endlessPlayback: endlessPlayback, endlessPlayback: endlessPlayback,
playerWakelock: playerWakelock, playerWakelock: playerWakelock,
), ),
@ -4073,11 +4074,11 @@ class $$PreferencesTableTableTableManager extends RootTableManager<
Value<String> downloadLocation = const Value.absent(), Value<String> downloadLocation = const Value.absent(),
Value<List<String>> localLibraryLocation = const Value.absent(), Value<List<String>> localLibraryLocation = const Value.absent(),
Value<String> pipedInstance = const Value.absent(), Value<String> pipedInstance = const Value.absent(),
Value<String> neteaseApiInstance = const Value.absent(),
Value<ThemeMode> themeMode = const Value.absent(), Value<ThemeMode> themeMode = const Value.absent(),
Value<AudioSource> audioSource = const Value.absent(), Value<AudioSource> audioSource = const Value.absent(),
Value<SourceCodecs> streamMusicCodec = const Value.absent(), Value<SourceCodecs> streamMusicCodec = const Value.absent(),
Value<SourceCodecs> downloadMusicCodec = const Value.absent(), Value<SourceCodecs> downloadMusicCodec = const Value.absent(),
Value<bool> discordPresence = const Value.absent(),
Value<bool> endlessPlayback = const Value.absent(), Value<bool> endlessPlayback = const Value.absent(),
Value<bool> playerWakelock = const Value.absent(), Value<bool> playerWakelock = const Value.absent(),
}) => }) =>
@ -4099,11 +4100,11 @@ class $$PreferencesTableTableTableManager extends RootTableManager<
downloadLocation: downloadLocation, downloadLocation: downloadLocation,
localLibraryLocation: localLibraryLocation, localLibraryLocation: localLibraryLocation,
pipedInstance: pipedInstance, pipedInstance: pipedInstance,
neteaseApiInstance: neteaseApiInstance,
themeMode: themeMode, themeMode: themeMode,
audioSource: audioSource, audioSource: audioSource,
streamMusicCodec: streamMusicCodec, streamMusicCodec: streamMusicCodec,
downloadMusicCodec: downloadMusicCodec, downloadMusicCodec: downloadMusicCodec,
discordPresence: discordPresence,
endlessPlayback: endlessPlayback, endlessPlayback: endlessPlayback,
playerWakelock: playerWakelock, playerWakelock: playerWakelock,
), ),
@ -4214,6 +4215,11 @@ class $$PreferencesTableTableFilterComposer
builder: (column, joinBuilders) => builder: (column, joinBuilders) =>
ColumnFilters(column, joinBuilders: joinBuilders)); ColumnFilters(column, joinBuilders: joinBuilders));
ColumnFilters<String> get neteaseApiInstance => $state.composableBuilder(
column: $state.table.neteaseApiInstance,
builder: (column, joinBuilders) =>
ColumnFilters(column, joinBuilders: joinBuilders));
ColumnWithTypeConverterFilters<ThemeMode, ThemeMode, String> get themeMode => ColumnWithTypeConverterFilters<ThemeMode, ThemeMode, String> get themeMode =>
$state.composableBuilder( $state.composableBuilder(
column: $state.table.themeMode, column: $state.table.themeMode,
@ -4242,11 +4248,6 @@ class $$PreferencesTableTableFilterComposer
column, column,
joinBuilders: joinBuilders)); joinBuilders: joinBuilders));
ColumnFilters<bool> get discordPresence => $state.composableBuilder(
column: $state.table.discordPresence,
builder: (column, joinBuilders) =>
ColumnFilters(column, joinBuilders: joinBuilders));
ColumnFilters<bool> get endlessPlayback => $state.composableBuilder( ColumnFilters<bool> get endlessPlayback => $state.composableBuilder(
column: $state.table.endlessPlayback, column: $state.table.endlessPlayback,
builder: (column, joinBuilders) => builder: (column, joinBuilders) =>
@ -4346,6 +4347,11 @@ class $$PreferencesTableTableOrderingComposer
builder: (column, joinBuilders) => builder: (column, joinBuilders) =>
ColumnOrderings(column, joinBuilders: joinBuilders)); ColumnOrderings(column, joinBuilders: joinBuilders));
ColumnOrderings<String> get neteaseApiInstance => $state.composableBuilder(
column: $state.table.neteaseApiInstance,
builder: (column, joinBuilders) =>
ColumnOrderings(column, joinBuilders: joinBuilders));
ColumnOrderings<String> get themeMode => $state.composableBuilder( ColumnOrderings<String> get themeMode => $state.composableBuilder(
column: $state.table.themeMode, column: $state.table.themeMode,
builder: (column, joinBuilders) => builder: (column, joinBuilders) =>
@ -4366,11 +4372,6 @@ class $$PreferencesTableTableOrderingComposer
builder: (column, joinBuilders) => builder: (column, joinBuilders) =>
ColumnOrderings(column, joinBuilders: joinBuilders)); ColumnOrderings(column, joinBuilders: joinBuilders));
ColumnOrderings<bool> get discordPresence => $state.composableBuilder(
column: $state.table.discordPresence,
builder: (column, joinBuilders) =>
ColumnOrderings(column, joinBuilders: joinBuilders));
ColumnOrderings<bool> get endlessPlayback => $state.composableBuilder( ColumnOrderings<bool> get endlessPlayback => $state.composableBuilder(
column: $state.table.endlessPlayback, column: $state.table.endlessPlayback,
builder: (column, joinBuilders) => builder: (column, joinBuilders) =>

View File

@ -13,7 +13,8 @@ enum CloseBehavior {
enum AudioSource { enum AudioSource {
youtube, youtube,
piped; piped,
netease;
String get label => name[0].toUpperCase() + name.substring(1); String get label => name[0].toUpperCase() + name.substring(1);
} }
@ -74,6 +75,8 @@ class PreferencesTable extends Table {
text().withDefault(const Constant('')).map(const StringListConverter())(); text().withDefault(const Constant('')).map(const StringListConverter())();
TextColumn get pipedInstance => TextColumn get pipedInstance =>
text().withDefault(const Constant('https://pipedapi.kavin.rocks'))(); text().withDefault(const Constant('https://pipedapi.kavin.rocks'))();
TextColumn get neteaseApiInstance => text().withDefault(
const Constant('https://rhythmbox-netease-music-api.vercel.app'))();
TextColumn get themeMode => TextColumn get themeMode =>
textEnum<ThemeMode>().withDefault(Constant(ThemeMode.system.name))(); textEnum<ThemeMode>().withDefault(Constant(ThemeMode.system.name))();
TextColumn get audioSource => TextColumn get audioSource =>
@ -82,8 +85,6 @@ class PreferencesTable extends Table {
textEnum<SourceCodecs>().withDefault(Constant(SourceCodecs.weba.name))(); textEnum<SourceCodecs>().withDefault(Constant(SourceCodecs.weba.name))();
TextColumn get downloadMusicCodec => TextColumn get downloadMusicCodec =>
textEnum<SourceCodecs>().withDefault(Constant(SourceCodecs.m4a.name))(); textEnum<SourceCodecs>().withDefault(Constant(SourceCodecs.m4a.name))();
BoolColumn get discordPresence =>
boolean().withDefault(const Constant(true))();
BoolColumn get endlessPlayback => BoolColumn get endlessPlayback =>
boolean().withDefault(const Constant(true))(); boolean().withDefault(const Constant(true))();
BoolColumn get playerWakelock => BoolColumn get playerWakelock =>
@ -108,12 +109,12 @@ class PreferencesTable extends Table {
searchMode: SearchMode.youtube, searchMode: SearchMode.youtube,
downloadLocation: '', downloadLocation: '',
localLibraryLocation: [], localLibraryLocation: [],
neteaseApiInstance: 'https://rhythmbox-netease-music-api.vercel.app',
pipedInstance: 'https://pipedapi.kavin.rocks', pipedInstance: 'https://pipedapi.kavin.rocks',
themeMode: ThemeMode.system, themeMode: ThemeMode.system,
audioSource: AudioSource.youtube, audioSource: AudioSource.youtube,
streamMusicCodec: SourceCodecs.weba, streamMusicCodec: SourceCodecs.weba,
downloadMusicCodec: SourceCodecs.m4a, downloadMusicCodec: SourceCodecs.m4a,
discordPresence: true,
endlessPlayback: true, endlessPlayback: true,
playerWakelock: true, playerWakelock: true,
); );

View File

@ -2,7 +2,8 @@ part of '../database.dart';
enum SourceType { enum SourceType {
youtube._('YouTube'), youtube._('YouTube'),
youtubeMusic._('YouTube Music'); youtubeMusic._('YouTube Music'),
netease._('Netease Music');
final String label; final String label;

View File

@ -86,7 +86,7 @@ abstract class AudioPlayerInterface {
), ),
) { ) {
_mkPlayer.stream.error.listen((event) { _mkPlayer.stream.error.listen((event) {
Get.find<ErrorNotifier>().logError('[Playback] Error: $event'); Get.find<ErrorNotifier>().logError('[Playback][Media] Error: $event');
}); });
} }

View File

@ -6,6 +6,7 @@ import 'package:rhythm_box/providers/error_notifier.dart';
import 'package:rhythm_box/services/audio_player/audio_player.dart'; import 'package:rhythm_box/services/audio_player/audio_player.dart';
import 'package:rhythm_box/services/server/active_sourced_track.dart'; import 'package:rhythm_box/services/server/active_sourced_track.dart';
import 'package:rhythm_box/services/server/sourced_track.dart'; import 'package:rhythm_box/services/server/sourced_track.dart';
import 'package:rhythm_box/services/sourced_track/sources/netease.dart';
import 'package:shelf/shelf.dart'; import 'package:shelf/shelf.dart';
class ServerPlaybackRoutesProvider { class ServerPlaybackRoutesProvider {
@ -24,14 +25,25 @@ class ServerPlaybackRoutesProvider {
activeSourcedTrack.updateTrack(sourcedTrack); activeSourcedTrack.updateTrack(sourcedTrack);
var url = sourcedTrack!.url;
if (sourcedTrack is NeteaseSourcedTrack) {
// Special processing for netease to get real assets url
final resp = await GetConnect(timeout: const Duration(seconds: 30)).get(
'${sourcedTrack.url}&realIP=${await NeteaseSourcedTrack.lookupRealIp()}',
);
final realUrl = resp.body['data'][0]['url'];
url = realUrl;
}
final res = await Dio().get( final res = await Dio().get(
sourcedTrack!.url, url,
options: Options( options: Options(
headers: { headers: {
...request.headers, ...request.headers,
'User-Agent': 'User-Agent':
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36',
'host': Uri.parse(sourcedTrack.url).host, 'host': Uri.parse(url).host,
'Cache-Control': 'max-age=0', 'Cache-Control': 'max-age=0',
'Connection': 'keep-alive', 'Connection': 'keep-alive',
}, },

View File

@ -2,6 +2,7 @@ import 'package:collection/collection.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:rhythm_box/providers/user_preferences.dart'; import 'package:rhythm_box/providers/user_preferences.dart';
import 'package:rhythm_box/services/database/database.dart'; import 'package:rhythm_box/services/database/database.dart';
import 'package:rhythm_box/services/sourced_track/sources/netease.dart';
import 'package:rhythm_box/services/utils.dart'; import 'package:rhythm_box/services/utils.dart';
import 'package:spotify/spotify.dart'; import 'package:spotify/spotify.dart';
@ -55,6 +56,12 @@ abstract class SourcedTrack extends Track {
.cast<SourceInfo>(); .cast<SourceInfo>();
return switch (audioSource) { return switch (audioSource) {
AudioSource.netease => NeteaseSourcedTrack(
source: source,
siblings: siblings,
sourceInfo: sourceInfo,
track: track,
),
AudioSource.piped => PipedSourcedTrack( AudioSource.piped => PipedSourcedTrack(
source: source, source: source,
siblings: siblings, siblings: siblings,
@ -94,14 +101,20 @@ abstract class SourcedTrack extends Track {
try { try {
return switch (audioSource) { return switch (audioSource) {
AudioSource.netease =>
await NeteaseSourcedTrack.fetchFromTrack(track: track),
AudioSource.piped => AudioSource.piped =>
await PipedSourcedTrack.fetchFromTrack(track: track), await PipedSourcedTrack.fetchFromTrack(track: track),
_ => await YoutubeSourcedTrack.fetchFromTrack(track: track), _ => await YoutubeSourcedTrack.fetchFromTrack(track: track),
}; };
} on TrackNotFoundError catch (_) { } on TrackNotFoundError catch (_) {
// TODO Try to look it up in other source return switch (preferences.audioSource) {
// But the youtube and piped.video are the same, and there is no extra sources, so i ignored this for temporary AudioSource.piped ||
rethrow; AudioSource.youtube =>
await NeteaseSourcedTrack.fetchFromTrack(track: track),
AudioSource.netease =>
await YoutubeSourcedTrack.fetchFromTrack(track: track),
};
} on HttpClientClosedException catch (_) { } on HttpClientClosedException catch (_) {
return await PipedSourcedTrack.fetchFromTrack(track: track); return await PipedSourcedTrack.fetchFromTrack(track: track);
} on VideoUnplayableException catch (_) { } on VideoUnplayableException catch (_) {

View File

@ -0,0 +1,235 @@
import 'package:collection/collection.dart';
import 'package:drift/drift.dart';
import 'package:get/get.dart' hide Value;
import 'package:rhythm_box/providers/database.dart';
import 'package:rhythm_box/providers/user_preferences.dart';
import 'package:rhythm_box/services/database/database.dart';
import 'package:spotify/spotify.dart';
import 'package:rhythm_box/services/sourced_track/enums.dart';
import 'package:rhythm_box/services/sourced_track/exceptions.dart';
import 'package:rhythm_box/services/sourced_track/models/source_info.dart';
import 'package:rhythm_box/services/sourced_track/models/source_map.dart';
import 'package:rhythm_box/services/sourced_track/sourced_track.dart';
class NeteaseSourceInfo extends SourceInfo {
NeteaseSourceInfo({
required super.id,
required super.title,
required super.artist,
required super.thumbnail,
required super.pageUrl,
required super.duration,
required super.artistUrl,
required super.album,
});
}
class NeteaseSourcedTrack extends SourcedTrack {
NeteaseSourcedTrack({
required super.source,
required super.siblings,
required super.sourceInfo,
required super.track,
});
static String _getBaseUrl() {
final preferences = Get.find<UserPreferencesProvider>().state.value;
return preferences.neteaseApiInstance;
}
static GetConnect _getClient() {
final client = GetConnect();
client.baseUrl = _getBaseUrl();
client.timeout = const Duration(seconds: 30);
return client;
}
static String? _lookedUpRealIp;
static Future<String> lookupRealIp() async {
if (_lookedUpRealIp != null) return _lookedUpRealIp!;
const ipCheckUrl = 'https://api.ipify.org';
final client = GetConnect(timeout: const Duration(seconds: 30));
final resp = await client.get(ipCheckUrl);
_lookedUpRealIp = resp.body;
return _lookedUpRealIp!;
}
static Future<NeteaseSourcedTrack> fetchFromTrack({
required Track track,
}) async {
final DatabaseProvider db = Get.find();
final cachedSource = await (db.database.select(db.database.sourceMatchTable)
..where((s) => s.trackId.equals(track.id!))
..limit(1)
..orderBy([
(s) =>
OrderingTerm(expression: s.createdAt, mode: OrderingMode.desc),
]))
.get()
.then((s) => s.firstOrNull);
if (cachedSource == null) {
final siblings = await fetchSiblings(track: track);
if (siblings.isEmpty) {
throw TrackNotFoundError(track);
}
await db.database.into(db.database.sourceMatchTable).insert(
SourceMatchTableCompanion.insert(
trackId: track.id!,
sourceId: siblings.first.info.id,
sourceType: const Value(SourceType.netease),
),
);
return NeteaseSourcedTrack(
siblings: siblings.map((s) => s.info).skip(1).toList(),
source: siblings.first.source as SourceMap,
sourceInfo: siblings.first.info,
track: track,
);
}
final client = _getClient();
final resp = await client.get('/song/detail?ids=${cachedSource.sourceId}');
final item = resp.body['songs'][0];
return NeteaseSourcedTrack(
siblings: [],
source: toSourceMap(item),
sourceInfo: NeteaseSourceInfo(
id: item['id'].toString(),
artist: item['ar'].map((x) => x['name']).join(','),
artistUrl: 'https://music.163.com/#/artist?id=${item['ar'][0]['id']}',
pageUrl: 'https://music.163.com/#/song?id=${item['id']}',
thumbnail: item['al']['picUrl'],
title: item['name'],
duration: Duration(milliseconds: item['dt']),
album: item['al']['name'],
),
track: track,
);
}
static SourceMap toSourceMap(dynamic manifest) {
final baseUrl = _getBaseUrl();
// Due to netease may provide m4a, mp3 and others, we cannot decide this so mock this data.
return SourceMap(
m4a: SourceQualityMap(
high: '$baseUrl/song/url?id=${manifest['id']}',
medium: '$baseUrl/song/url?id=${manifest['id']}&br=192000',
low: '$baseUrl/song/url?id=${manifest['id']}&br=128000',
),
weba: SourceQualityMap(
high: '$baseUrl/song/url?id=${manifest['id']}',
medium: '$baseUrl/song/url?id=${manifest['id']}&br=192000',
low: '$baseUrl/song/url?id=${manifest['id']}&br=128000',
),
);
}
static Future<List<SiblingType>> fetchSiblings({
required Track track,
}) async {
final query = SourcedTrack.getSearchTerm(track);
final client = _getClient();
final resp =
await client.get('/search?keywords=${Uri.encodeComponent(query)}');
final results = resp.body['result']['songs'];
// We can just trust netease music for now
// If we need to check is the result correct, refer to this code
// https://github.com/KRTirtho/spotube/blob/9b024120601c0d381edeab4460cb22f87149d0f8/lib/services/sourced_track/sources/jiosaavn.dart#L129
final matchedResults = results.map(toSiblingType).toList();
return matchedResults.cast<SiblingType>();
}
@override
Future<NeteaseSourcedTrack> copyWithSibling() async {
if (siblings.isNotEmpty) {
return this;
}
final fetchedSiblings = await fetchSiblings(track: this);
return NeteaseSourcedTrack(
siblings: fetchedSiblings
.where((s) => s.info.id != sourceInfo.id)
.map((s) => s.info)
.toList(),
source: source,
sourceInfo: sourceInfo,
track: this,
);
}
@override
Future<NeteaseSourcedTrack?> swapWithSibling(SourceInfo sibling) async {
if (sibling.id == sourceInfo.id) {
return null;
}
// a sibling source that was fetched from the search results
final isStepSibling = siblings.none((s) => s.id == sibling.id);
final newSourceInfo = isStepSibling
? sibling
: siblings.firstWhere((s) => s.id == sibling.id);
final newSiblings = siblings.where((s) => s.id != sibling.id).toList()
..insert(0, sourceInfo);
final client = _getClient();
final resp = await client.get('/song/detail?ids=${newSourceInfo.id}');
final item = resp.body['songs'][0];
final (:info, :source) = toSiblingType(item);
final db = Get.find<DatabaseProvider>();
await db.database.into(db.database.sourceMatchTable).insert(
SourceMatchTableCompanion.insert(
trackId: id!,
sourceId: info.id,
sourceType: const Value(SourceType.netease),
// Because we're sorting by createdAt in the query
// we have to update it to indicate priority
createdAt: Value(DateTime.now()),
),
mode: InsertMode.replace,
);
return NeteaseSourcedTrack(
siblings: newSiblings,
source: source!,
sourceInfo: info,
track: this,
);
}
static SiblingType toSiblingType(dynamic item) {
final firstArtist = item['ar'] != null ? item['ar'][0] : item['artists'][0];
final SiblingType sibling = (
info: NeteaseSourceInfo(
id: item['id'].toString(),
artist: item['ar'] != null
? item['ar'].map((x) => x['name']).join(',')
: item['artists'].map((x) => x['name']).toString(),
artistUrl: 'https://music.163.com/#/artist?id=${firstArtist['id']}',
pageUrl: 'https://music.163.com/#/song?id=${item['id']}',
thumbnail: item['al']?['picUrl'] ??
'https://p1.music.126.net/6y-UleORITEDbvrOLV0Q8A==/5639395138885805.jpg',
title: item['name'],
duration: item['dt'] != null
? Duration(milliseconds: item['dt'])
: Duration.zero,
album: item['al']?['name'],
),
source: toSourceMap(item),
);
return sibling;
}
}

View File

@ -85,7 +85,7 @@ class YoutubeSourcedTrack extends SourcedTrack {
cachedSource.sourceId, cachedSource.sourceId,
) )
.timeout( .timeout(
const Duration(seconds: 5), const Duration(seconds: 30),
onTimeout: () => throw ClientException('Timeout'), onTimeout: () => throw ClientException('Timeout'),
); );
return YoutubeSourcedTrack( return YoutubeSourcedTrack(
@ -140,7 +140,7 @@ class YoutubeSourcedTrack extends SourcedTrack {
if (index == 0) { if (index == 0) {
final manifest = final manifest =
await youtubeClient.videos.streamsClient.getManifest(item.id).timeout( await youtubeClient.videos.streamsClient.getManifest(item.id).timeout(
const Duration(seconds: 5), const Duration(seconds: 30),
onTimeout: () => throw ClientException('Timeout'), onTimeout: () => throw ClientException('Timeout'),
); );
sourceMap = toSourceMap(manifest); sourceMap = toSourceMap(manifest);
@ -285,7 +285,7 @@ class YoutubeSourcedTrack extends SourcedTrack {
final manifest = await youtubeClient.videos.streamsClient final manifest = await youtubeClient.videos.streamsClient
.getManifest(newSourceInfo.id) .getManifest(newSourceInfo.id)
.timeout( .timeout(
const Duration(seconds: 5), const Duration(seconds: 30),
onTimeout: () => throw ClientException('Timeout'), onTimeout: () => throw ClientException('Timeout'),
); );

View File

@ -347,10 +347,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: dio name: dio
sha256: "0dfb6b6a1979dac1c1245e17cef824d7b452ea29bd33d3467269f9bef3715fb0" sha256: "5598aa796bbf4699afd5c67c0f5f6e2ed542afc956884b9cd58c306966efc260"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "5.6.0" version: "5.7.0"
dio_web_adapter: dio_web_adapter:
dependency: transitive dependency: transitive
description: description:
@ -383,6 +383,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.19.1" version: "2.19.1"
dropdown_button2:
dependency: "direct main"
description:
name: dropdown_button2
sha256: b0fe8d49a030315e9eef6c7ac84ca964250155a6224d491c1365061bc974a9e1
url: "https://pub.dev"
source: hosted
version: "2.3.9"
duration: duration:
dependency: "direct main" dependency: "direct main"
description: description:
@ -1514,10 +1522,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: uuid name: uuid
sha256: "83d37c7ad7aaf9aa8e275490669535c8080377cfa7a7004c24dfac53afffaa90" sha256: f33d6bb662f0e4f79dcd7ada2e6170f3b3a2530c28fc41f49a411ddedd576a77
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.4.2" version: "4.5.0"
vector_math: vector_math:
dependency: transitive dependency: transitive
description: description:

View File

@ -101,6 +101,7 @@ dependencies:
timezone: ^0.9.4 timezone: ^0.9.4
url_launcher: ^6.3.0 url_launcher: ^6.3.0
wakelock_plus: ^1.2.8 wakelock_plus: ^1.2.8
dropdown_button2: ^2.3.9
dev_dependencies: dev_dependencies:
flutter_test: flutter_test: