mirror of
https://github.com/KRTirtho/spotube.git
synced 2025-09-13 07:55:18 +00:00
feat: add youtube engine abstraction and yt-dlp integration
This commit is contained in:
parent
698fb6ba27
commit
d6726bc3b0
1
drift_schemas/app_db/drift_schema_v4.json
Normal file
1
drift_schemas/app_db/drift_schema_v4.json
Normal file
File diff suppressed because one or more lines are too long
@ -134,4 +134,5 @@ abstract class SpotubeIcons {
|
|||||||
static const grid = FeatherIcons.grid;
|
static const grid = FeatherIcons.grid;
|
||||||
static const list = FeatherIcons.list;
|
static const list = FeatherIcons.list;
|
||||||
static const device = FeatherIcons.smartphone;
|
static const device = FeatherIcons.smartphone;
|
||||||
|
static const engine = FeatherIcons.server;
|
||||||
}
|
}
|
||||||
|
33
lib/hooks/configurators/use_check_yt_dlp_installed.dart
Normal file
33
lib/hooks/configurators/use_check_yt_dlp_installed.dart
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:shadcn_flutter/shadcn_flutter.dart';
|
||||||
|
import 'package:spotube/models/database/database.dart';
|
||||||
|
import 'package:spotube/modules/settings/youtube_engine_not_installed_dialog.dart';
|
||||||
|
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
|
||||||
|
import 'package:spotube/services/youtube_engine/yt_dlp_engine.dart';
|
||||||
|
|
||||||
|
void useCheckYtDlpInstalled(WidgetRef ref) {
|
||||||
|
final context = useContext();
|
||||||
|
|
||||||
|
useEffect(() {
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||||||
|
final youtubeEngine = ref.read(
|
||||||
|
userPreferencesProvider.select(
|
||||||
|
(value) => value.youtubeClientEngine,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (youtubeEngine == YoutubeClientEngine.ytDlp &&
|
||||||
|
!await YtDlpEngine.isInstalled() &&
|
||||||
|
context.mounted) {
|
||||||
|
await showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) =>
|
||||||
|
YouTubeEngineNotInstalledDialog(engine: youtubeEngine),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}, []);
|
||||||
|
}
|
@ -415,5 +415,9 @@
|
|||||||
"no_tracks_listened_yet": "Looks like you haven't listened to anything yet",
|
"no_tracks_listened_yet": "Looks like you haven't listened to anything yet",
|
||||||
"not_following_artists": "You're not following any artists",
|
"not_following_artists": "You're not following any artists",
|
||||||
"no_favorite_albums_yet": "Looks like you haven't added any albums to your favorites yet",
|
"no_favorite_albums_yet": "Looks like you haven't added any albums to your favorites yet",
|
||||||
"no_logs_found": "No logs found"
|
"no_logs_found": "No logs found",
|
||||||
|
"youtube_engine": "YouTube Engine",
|
||||||
|
"youtube_engine_not_installed_title": "{engine} is not installed",
|
||||||
|
"youtube_engine_not_installed_message": "{engine} is not installed in your system.\nPlease install it and make sure it's available in the PATH variable\n\nAfter installing, restart the app",
|
||||||
|
"download": "Download"
|
||||||
}
|
}
|
@ -50,6 +50,7 @@ import 'package:flutter_displaymode/flutter_displaymode.dart';
|
|||||||
import 'package:timezone/data/latest.dart' as tz;
|
import 'package:timezone/data/latest.dart' as tz;
|
||||||
import 'package:window_manager/window_manager.dart';
|
import 'package:window_manager/window_manager.dart';
|
||||||
import 'package:shadcn_flutter/shadcn_flutter.dart';
|
import 'package:shadcn_flutter/shadcn_flutter.dart';
|
||||||
|
import 'package:yt_dlp_dart/yt_dlp_dart.dart';
|
||||||
|
|
||||||
Future<void> main(List<String> rawArgs) async {
|
Future<void> main(List<String> rawArgs) async {
|
||||||
if (rawArgs.contains("web_view_title_bar")) {
|
if (rawArgs.contains("web_view_title_bar")) {
|
||||||
@ -79,15 +80,15 @@ Future<void> main(List<String> rawArgs) async {
|
|||||||
await FlutterDisplayMode.setHighRefreshRate();
|
await FlutterDisplayMode.setHighRefreshRate();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (kIsDesktop) {
|
|
||||||
await windowManager.setPreventClose(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!kIsWeb) {
|
if (!kIsWeb) {
|
||||||
MetadataGod.initialize();
|
MetadataGod.initialize();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (kIsDesktop) {
|
if (kIsDesktop) {
|
||||||
|
await windowManager.setPreventClose(true);
|
||||||
|
await YtDlp.instance
|
||||||
|
.setBinaryLocation("yt-dlp${kIsWindows ? '.exe' : ''}")
|
||||||
|
.catchError((e, stack) => null);
|
||||||
await FlutterDiscordRPC.initialize(Env.discordAppId);
|
await FlutterDiscordRPC.initialize(Env.discordAppId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,6 +18,8 @@ import 'package:spotube/services/sourced_track/enums.dart';
|
|||||||
import 'package:flutter/widgets.dart' hide Table, Key, View;
|
import 'package:flutter/widgets.dart' hide Table, Key, View;
|
||||||
import 'package:spotube/modules/settings/color_scheme_picker_dialog.dart';
|
import 'package:spotube/modules/settings/color_scheme_picker_dialog.dart';
|
||||||
import 'package:drift/native.dart';
|
import 'package:drift/native.dart';
|
||||||
|
import 'package:spotube/services/youtube_engine/youtube_explode_engine.dart';
|
||||||
|
import 'package:spotube/services/youtube_engine/yt_dlp_engine.dart';
|
||||||
import 'package:sqlite3/sqlite3.dart';
|
import 'package:sqlite3/sqlite3.dart';
|
||||||
import 'package:sqlite3_flutter_libs/sqlite3_flutter_libs.dart';
|
import 'package:sqlite3_flutter_libs/sqlite3_flutter_libs.dart';
|
||||||
|
|
||||||
@ -59,7 +61,7 @@ class AppDatabase extends _$AppDatabase {
|
|||||||
AppDatabase() : super(_openConnection());
|
AppDatabase() : super(_openConnection());
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get schemaVersion => 3;
|
int get schemaVersion => 4;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
MigrationStrategy get migration {
|
MigrationStrategy get migration {
|
||||||
@ -78,6 +80,12 @@ class AppDatabase extends _$AppDatabase {
|
|||||||
schema.preferencesTable.cacheMusic,
|
schema.preferencesTable.cacheMusic,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
from3To4: (m, schema) async {
|
||||||
|
await m.addColumn(
|
||||||
|
schema.preferencesTable,
|
||||||
|
schema.preferencesTable.youtubeClientEngine,
|
||||||
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -760,6 +760,17 @@ class $PreferencesTableTable extends PreferencesTable
|
|||||||
defaultValue: Constant(AudioSource.youtube.name))
|
defaultValue: Constant(AudioSource.youtube.name))
|
||||||
.withConverter<AudioSource>(
|
.withConverter<AudioSource>(
|
||||||
$PreferencesTableTable.$converteraudioSource);
|
$PreferencesTableTable.$converteraudioSource);
|
||||||
|
static const VerificationMeta _youtubeClientEngineMeta =
|
||||||
|
const VerificationMeta('youtubeClientEngine');
|
||||||
|
@override
|
||||||
|
late final GeneratedColumnWithTypeConverter<YoutubeClientEngine, String>
|
||||||
|
youtubeClientEngine = GeneratedColumn<String>(
|
||||||
|
'youtube_client_engine', aliasedName, false,
|
||||||
|
type: DriftSqlType.string,
|
||||||
|
requiredDuringInsert: false,
|
||||||
|
defaultValue: Constant(YoutubeClientEngine.youtubeExplode.name))
|
||||||
|
.withConverter<YoutubeClientEngine>(
|
||||||
|
$PreferencesTableTable.$converteryoutubeClientEngine);
|
||||||
static const VerificationMeta _streamMusicCodecMeta =
|
static const VerificationMeta _streamMusicCodecMeta =
|
||||||
const VerificationMeta('streamMusicCodec');
|
const VerificationMeta('streamMusicCodec');
|
||||||
@override
|
@override
|
||||||
@ -845,6 +856,7 @@ class $PreferencesTableTable extends PreferencesTable
|
|||||||
invidiousInstance,
|
invidiousInstance,
|
||||||
themeMode,
|
themeMode,
|
||||||
audioSource,
|
audioSource,
|
||||||
|
youtubeClientEngine,
|
||||||
streamMusicCodec,
|
streamMusicCodec,
|
||||||
downloadMusicCodec,
|
downloadMusicCodec,
|
||||||
discordPresence,
|
discordPresence,
|
||||||
@ -937,6 +949,8 @@ class $PreferencesTableTable extends PreferencesTable
|
|||||||
}
|
}
|
||||||
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(
|
||||||
|
_youtubeClientEngineMeta, 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')) {
|
if (data.containsKey('discord_presence')) {
|
||||||
@ -1025,6 +1039,9 @@ class $PreferencesTableTable extends PreferencesTable
|
|||||||
audioSource: $PreferencesTableTable.$converteraudioSource.fromSql(
|
audioSource: $PreferencesTableTable.$converteraudioSource.fromSql(
|
||||||
attachedDatabase.typeMapping.read(
|
attachedDatabase.typeMapping.read(
|
||||||
DriftSqlType.string, data['${effectivePrefix}audio_source'])!),
|
DriftSqlType.string, data['${effectivePrefix}audio_source'])!),
|
||||||
|
youtubeClientEngine: $PreferencesTableTable.$converteryoutubeClientEngine
|
||||||
|
.fromSql(attachedDatabase.typeMapping.read(DriftSqlType.string,
|
||||||
|
data['${effectivePrefix}youtube_client_engine'])!),
|
||||||
streamMusicCodec: $PreferencesTableTable.$converterstreamMusicCodec
|
streamMusicCodec: $PreferencesTableTable.$converterstreamMusicCodec
|
||||||
.fromSql(attachedDatabase.typeMapping.read(DriftSqlType.string,
|
.fromSql(attachedDatabase.typeMapping.read(DriftSqlType.string,
|
||||||
data['${effectivePrefix}stream_music_codec'])!),
|
data['${effectivePrefix}stream_music_codec'])!),
|
||||||
@ -1069,6 +1086,9 @@ class $PreferencesTableTable extends PreferencesTable
|
|||||||
const EnumNameConverter<ThemeMode>(ThemeMode.values);
|
const EnumNameConverter<ThemeMode>(ThemeMode.values);
|
||||||
static JsonTypeConverter2<AudioSource, String, String> $converteraudioSource =
|
static JsonTypeConverter2<AudioSource, String, String> $converteraudioSource =
|
||||||
const EnumNameConverter<AudioSource>(AudioSource.values);
|
const EnumNameConverter<AudioSource>(AudioSource.values);
|
||||||
|
static JsonTypeConverter2<YoutubeClientEngine, String, String>
|
||||||
|
$converteryoutubeClientEngine =
|
||||||
|
const EnumNameConverter<YoutubeClientEngine>(YoutubeClientEngine.values);
|
||||||
static JsonTypeConverter2<SourceCodecs, String, String>
|
static JsonTypeConverter2<SourceCodecs, String, String>
|
||||||
$converterstreamMusicCodec =
|
$converterstreamMusicCodec =
|
||||||
const EnumNameConverter<SourceCodecs>(SourceCodecs.values);
|
const EnumNameConverter<SourceCodecs>(SourceCodecs.values);
|
||||||
@ -1100,6 +1120,7 @@ class PreferencesTableData extends DataClass
|
|||||||
final String invidiousInstance;
|
final String invidiousInstance;
|
||||||
final ThemeMode themeMode;
|
final ThemeMode themeMode;
|
||||||
final AudioSource audioSource;
|
final AudioSource audioSource;
|
||||||
|
final YoutubeClientEngine youtubeClientEngine;
|
||||||
final SourceCodecs streamMusicCodec;
|
final SourceCodecs streamMusicCodec;
|
||||||
final SourceCodecs downloadMusicCodec;
|
final SourceCodecs downloadMusicCodec;
|
||||||
final bool discordPresence;
|
final bool discordPresence;
|
||||||
@ -1128,6 +1149,7 @@ class PreferencesTableData extends DataClass
|
|||||||
required this.invidiousInstance,
|
required this.invidiousInstance,
|
||||||
required this.themeMode,
|
required this.themeMode,
|
||||||
required this.audioSource,
|
required this.audioSource,
|
||||||
|
required this.youtubeClientEngine,
|
||||||
required this.streamMusicCodec,
|
required this.streamMusicCodec,
|
||||||
required this.downloadMusicCodec,
|
required this.downloadMusicCodec,
|
||||||
required this.discordPresence,
|
required this.discordPresence,
|
||||||
@ -1190,6 +1212,11 @@ class PreferencesTableData extends DataClass
|
|||||||
map['audio_source'] = Variable<String>(
|
map['audio_source'] = Variable<String>(
|
||||||
$PreferencesTableTable.$converteraudioSource.toSql(audioSource));
|
$PreferencesTableTable.$converteraudioSource.toSql(audioSource));
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
map['youtube_client_engine'] = Variable<String>($PreferencesTableTable
|
||||||
|
.$converteryoutubeClientEngine
|
||||||
|
.toSql(youtubeClientEngine));
|
||||||
|
}
|
||||||
{
|
{
|
||||||
map['stream_music_codec'] = Variable<String>($PreferencesTableTable
|
map['stream_music_codec'] = Variable<String>($PreferencesTableTable
|
||||||
.$converterstreamMusicCodec
|
.$converterstreamMusicCodec
|
||||||
@ -1230,6 +1257,7 @@ class PreferencesTableData extends DataClass
|
|||||||
invidiousInstance: Value(invidiousInstance),
|
invidiousInstance: Value(invidiousInstance),
|
||||||
themeMode: Value(themeMode),
|
themeMode: Value(themeMode),
|
||||||
audioSource: Value(audioSource),
|
audioSource: Value(audioSource),
|
||||||
|
youtubeClientEngine: Value(youtubeClientEngine),
|
||||||
streamMusicCodec: Value(streamMusicCodec),
|
streamMusicCodec: Value(streamMusicCodec),
|
||||||
downloadMusicCodec: Value(downloadMusicCodec),
|
downloadMusicCodec: Value(downloadMusicCodec),
|
||||||
discordPresence: Value(discordPresence),
|
discordPresence: Value(discordPresence),
|
||||||
@ -1273,6 +1301,8 @@ class PreferencesTableData extends DataClass
|
|||||||
.fromJson(serializer.fromJson<String>(json['themeMode'])),
|
.fromJson(serializer.fromJson<String>(json['themeMode'])),
|
||||||
audioSource: $PreferencesTableTable.$converteraudioSource
|
audioSource: $PreferencesTableTable.$converteraudioSource
|
||||||
.fromJson(serializer.fromJson<String>(json['audioSource'])),
|
.fromJson(serializer.fromJson<String>(json['audioSource'])),
|
||||||
|
youtubeClientEngine: $PreferencesTableTable.$converteryoutubeClientEngine
|
||||||
|
.fromJson(serializer.fromJson<String>(json['youtubeClientEngine'])),
|
||||||
streamMusicCodec: $PreferencesTableTable.$converterstreamMusicCodec
|
streamMusicCodec: $PreferencesTableTable.$converterstreamMusicCodec
|
||||||
.fromJson(serializer.fromJson<String>(json['streamMusicCodec'])),
|
.fromJson(serializer.fromJson<String>(json['streamMusicCodec'])),
|
||||||
downloadMusicCodec: $PreferencesTableTable.$converterdownloadMusicCodec
|
downloadMusicCodec: $PreferencesTableTable.$converterdownloadMusicCodec
|
||||||
@ -1316,6 +1346,9 @@ class PreferencesTableData extends DataClass
|
|||||||
$PreferencesTableTable.$converterthemeMode.toJson(themeMode)),
|
$PreferencesTableTable.$converterthemeMode.toJson(themeMode)),
|
||||||
'audioSource': serializer.toJson<String>(
|
'audioSource': serializer.toJson<String>(
|
||||||
$PreferencesTableTable.$converteraudioSource.toJson(audioSource)),
|
$PreferencesTableTable.$converteraudioSource.toJson(audioSource)),
|
||||||
|
'youtubeClientEngine': serializer.toJson<String>($PreferencesTableTable
|
||||||
|
.$converteryoutubeClientEngine
|
||||||
|
.toJson(youtubeClientEngine)),
|
||||||
'streamMusicCodec': serializer.toJson<String>($PreferencesTableTable
|
'streamMusicCodec': serializer.toJson<String>($PreferencesTableTable
|
||||||
.$converterstreamMusicCodec
|
.$converterstreamMusicCodec
|
||||||
.toJson(streamMusicCodec)),
|
.toJson(streamMusicCodec)),
|
||||||
@ -1351,6 +1384,7 @@ class PreferencesTableData extends DataClass
|
|||||||
String? invidiousInstance,
|
String? invidiousInstance,
|
||||||
ThemeMode? themeMode,
|
ThemeMode? themeMode,
|
||||||
AudioSource? audioSource,
|
AudioSource? audioSource,
|
||||||
|
YoutubeClientEngine? youtubeClientEngine,
|
||||||
SourceCodecs? streamMusicCodec,
|
SourceCodecs? streamMusicCodec,
|
||||||
SourceCodecs? downloadMusicCodec,
|
SourceCodecs? downloadMusicCodec,
|
||||||
bool? discordPresence,
|
bool? discordPresence,
|
||||||
@ -1379,6 +1413,7 @@ class PreferencesTableData extends DataClass
|
|||||||
invidiousInstance: invidiousInstance ?? this.invidiousInstance,
|
invidiousInstance: invidiousInstance ?? this.invidiousInstance,
|
||||||
themeMode: themeMode ?? this.themeMode,
|
themeMode: themeMode ?? this.themeMode,
|
||||||
audioSource: audioSource ?? this.audioSource,
|
audioSource: audioSource ?? this.audioSource,
|
||||||
|
youtubeClientEngine: youtubeClientEngine ?? this.youtubeClientEngine,
|
||||||
streamMusicCodec: streamMusicCodec ?? this.streamMusicCodec,
|
streamMusicCodec: streamMusicCodec ?? this.streamMusicCodec,
|
||||||
downloadMusicCodec: downloadMusicCodec ?? this.downloadMusicCodec,
|
downloadMusicCodec: downloadMusicCodec ?? this.downloadMusicCodec,
|
||||||
discordPresence: discordPresence ?? this.discordPresence,
|
discordPresence: discordPresence ?? this.discordPresence,
|
||||||
@ -1439,6 +1474,9 @@ class PreferencesTableData extends DataClass
|
|||||||
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,
|
||||||
|
youtubeClientEngine: data.youtubeClientEngine.present
|
||||||
|
? data.youtubeClientEngine.value
|
||||||
|
: this.youtubeClientEngine,
|
||||||
streamMusicCodec: data.streamMusicCodec.present
|
streamMusicCodec: data.streamMusicCodec.present
|
||||||
? data.streamMusicCodec.value
|
? data.streamMusicCodec.value
|
||||||
: this.streamMusicCodec,
|
: this.streamMusicCodec,
|
||||||
@ -1483,6 +1521,7 @@ class PreferencesTableData extends DataClass
|
|||||||
..write('invidiousInstance: $invidiousInstance, ')
|
..write('invidiousInstance: $invidiousInstance, ')
|
||||||
..write('themeMode: $themeMode, ')
|
..write('themeMode: $themeMode, ')
|
||||||
..write('audioSource: $audioSource, ')
|
..write('audioSource: $audioSource, ')
|
||||||
|
..write('youtubeClientEngine: $youtubeClientEngine, ')
|
||||||
..write('streamMusicCodec: $streamMusicCodec, ')
|
..write('streamMusicCodec: $streamMusicCodec, ')
|
||||||
..write('downloadMusicCodec: $downloadMusicCodec, ')
|
..write('downloadMusicCodec: $downloadMusicCodec, ')
|
||||||
..write('discordPresence: $discordPresence, ')
|
..write('discordPresence: $discordPresence, ')
|
||||||
@ -1516,6 +1555,7 @@ class PreferencesTableData extends DataClass
|
|||||||
invidiousInstance,
|
invidiousInstance,
|
||||||
themeMode,
|
themeMode,
|
||||||
audioSource,
|
audioSource,
|
||||||
|
youtubeClientEngine,
|
||||||
streamMusicCodec,
|
streamMusicCodec,
|
||||||
downloadMusicCodec,
|
downloadMusicCodec,
|
||||||
discordPresence,
|
discordPresence,
|
||||||
@ -1548,6 +1588,7 @@ class PreferencesTableData extends DataClass
|
|||||||
other.invidiousInstance == this.invidiousInstance &&
|
other.invidiousInstance == this.invidiousInstance &&
|
||||||
other.themeMode == this.themeMode &&
|
other.themeMode == this.themeMode &&
|
||||||
other.audioSource == this.audioSource &&
|
other.audioSource == this.audioSource &&
|
||||||
|
other.youtubeClientEngine == this.youtubeClientEngine &&
|
||||||
other.streamMusicCodec == this.streamMusicCodec &&
|
other.streamMusicCodec == this.streamMusicCodec &&
|
||||||
other.downloadMusicCodec == this.downloadMusicCodec &&
|
other.downloadMusicCodec == this.downloadMusicCodec &&
|
||||||
other.discordPresence == this.discordPresence &&
|
other.discordPresence == this.discordPresence &&
|
||||||
@ -1578,6 +1619,7 @@ class PreferencesTableCompanion extends UpdateCompanion<PreferencesTableData> {
|
|||||||
final Value<String> invidiousInstance;
|
final Value<String> invidiousInstance;
|
||||||
final Value<ThemeMode> themeMode;
|
final Value<ThemeMode> themeMode;
|
||||||
final Value<AudioSource> audioSource;
|
final Value<AudioSource> audioSource;
|
||||||
|
final Value<YoutubeClientEngine> youtubeClientEngine;
|
||||||
final Value<SourceCodecs> streamMusicCodec;
|
final Value<SourceCodecs> streamMusicCodec;
|
||||||
final Value<SourceCodecs> downloadMusicCodec;
|
final Value<SourceCodecs> downloadMusicCodec;
|
||||||
final Value<bool> discordPresence;
|
final Value<bool> discordPresence;
|
||||||
@ -1606,6 +1648,7 @@ class PreferencesTableCompanion extends UpdateCompanion<PreferencesTableData> {
|
|||||||
this.invidiousInstance = const Value.absent(),
|
this.invidiousInstance = const Value.absent(),
|
||||||
this.themeMode = const Value.absent(),
|
this.themeMode = const Value.absent(),
|
||||||
this.audioSource = const Value.absent(),
|
this.audioSource = const Value.absent(),
|
||||||
|
this.youtubeClientEngine = 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.discordPresence = const Value.absent(),
|
||||||
@ -1635,6 +1678,7 @@ class PreferencesTableCompanion extends UpdateCompanion<PreferencesTableData> {
|
|||||||
this.invidiousInstance = const Value.absent(),
|
this.invidiousInstance = const Value.absent(),
|
||||||
this.themeMode = const Value.absent(),
|
this.themeMode = const Value.absent(),
|
||||||
this.audioSource = const Value.absent(),
|
this.audioSource = const Value.absent(),
|
||||||
|
this.youtubeClientEngine = 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.discordPresence = const Value.absent(),
|
||||||
@ -1664,6 +1708,7 @@ class PreferencesTableCompanion extends UpdateCompanion<PreferencesTableData> {
|
|||||||
Expression<String>? invidiousInstance,
|
Expression<String>? invidiousInstance,
|
||||||
Expression<String>? themeMode,
|
Expression<String>? themeMode,
|
||||||
Expression<String>? audioSource,
|
Expression<String>? audioSource,
|
||||||
|
Expression<String>? youtubeClientEngine,
|
||||||
Expression<String>? streamMusicCodec,
|
Expression<String>? streamMusicCodec,
|
||||||
Expression<String>? downloadMusicCodec,
|
Expression<String>? downloadMusicCodec,
|
||||||
Expression<bool>? discordPresence,
|
Expression<bool>? discordPresence,
|
||||||
@ -1695,6 +1740,8 @@ class PreferencesTableCompanion extends UpdateCompanion<PreferencesTableData> {
|
|||||||
if (invidiousInstance != null) 'invidious_instance': invidiousInstance,
|
if (invidiousInstance != null) 'invidious_instance': invidiousInstance,
|
||||||
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 (youtubeClientEngine != null)
|
||||||
|
'youtube_client_engine': youtubeClientEngine,
|
||||||
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,
|
||||||
@ -1727,6 +1774,7 @@ class PreferencesTableCompanion extends UpdateCompanion<PreferencesTableData> {
|
|||||||
Value<String>? invidiousInstance,
|
Value<String>? invidiousInstance,
|
||||||
Value<ThemeMode>? themeMode,
|
Value<ThemeMode>? themeMode,
|
||||||
Value<AudioSource>? audioSource,
|
Value<AudioSource>? audioSource,
|
||||||
|
Value<YoutubeClientEngine>? youtubeClientEngine,
|
||||||
Value<SourceCodecs>? streamMusicCodec,
|
Value<SourceCodecs>? streamMusicCodec,
|
||||||
Value<SourceCodecs>? downloadMusicCodec,
|
Value<SourceCodecs>? downloadMusicCodec,
|
||||||
Value<bool>? discordPresence,
|
Value<bool>? discordPresence,
|
||||||
@ -1755,6 +1803,7 @@ class PreferencesTableCompanion extends UpdateCompanion<PreferencesTableData> {
|
|||||||
invidiousInstance: invidiousInstance ?? this.invidiousInstance,
|
invidiousInstance: invidiousInstance ?? this.invidiousInstance,
|
||||||
themeMode: themeMode ?? this.themeMode,
|
themeMode: themeMode ?? this.themeMode,
|
||||||
audioSource: audioSource ?? this.audioSource,
|
audioSource: audioSource ?? this.audioSource,
|
||||||
|
youtubeClientEngine: youtubeClientEngine ?? this.youtubeClientEngine,
|
||||||
streamMusicCodec: streamMusicCodec ?? this.streamMusicCodec,
|
streamMusicCodec: streamMusicCodec ?? this.streamMusicCodec,
|
||||||
downloadMusicCodec: downloadMusicCodec ?? this.downloadMusicCodec,
|
downloadMusicCodec: downloadMusicCodec ?? this.downloadMusicCodec,
|
||||||
discordPresence: discordPresence ?? this.discordPresence,
|
discordPresence: discordPresence ?? this.discordPresence,
|
||||||
@ -1845,6 +1894,11 @@ class PreferencesTableCompanion extends UpdateCompanion<PreferencesTableData> {
|
|||||||
.$converteraudioSource
|
.$converteraudioSource
|
||||||
.toSql(audioSource.value));
|
.toSql(audioSource.value));
|
||||||
}
|
}
|
||||||
|
if (youtubeClientEngine.present) {
|
||||||
|
map['youtube_client_engine'] = Variable<String>($PreferencesTableTable
|
||||||
|
.$converteryoutubeClientEngine
|
||||||
|
.toSql(youtubeClientEngine.value));
|
||||||
|
}
|
||||||
if (streamMusicCodec.present) {
|
if (streamMusicCodec.present) {
|
||||||
map['stream_music_codec'] = Variable<String>($PreferencesTableTable
|
map['stream_music_codec'] = Variable<String>($PreferencesTableTable
|
||||||
.$converterstreamMusicCodec
|
.$converterstreamMusicCodec
|
||||||
@ -1894,6 +1948,7 @@ class PreferencesTableCompanion extends UpdateCompanion<PreferencesTableData> {
|
|||||||
..write('invidiousInstance: $invidiousInstance, ')
|
..write('invidiousInstance: $invidiousInstance, ')
|
||||||
..write('themeMode: $themeMode, ')
|
..write('themeMode: $themeMode, ')
|
||||||
..write('audioSource: $audioSource, ')
|
..write('audioSource: $audioSource, ')
|
||||||
|
..write('youtubeClientEngine: $youtubeClientEngine, ')
|
||||||
..write('streamMusicCodec: $streamMusicCodec, ')
|
..write('streamMusicCodec: $streamMusicCodec, ')
|
||||||
..write('downloadMusicCodec: $downloadMusicCodec, ')
|
..write('downloadMusicCodec: $downloadMusicCodec, ')
|
||||||
..write('discordPresence: $discordPresence, ')
|
..write('discordPresence: $discordPresence, ')
|
||||||
@ -4565,6 +4620,7 @@ typedef $$PreferencesTableTableCreateCompanionBuilder
|
|||||||
Value<String> invidiousInstance,
|
Value<String> invidiousInstance,
|
||||||
Value<ThemeMode> themeMode,
|
Value<ThemeMode> themeMode,
|
||||||
Value<AudioSource> audioSource,
|
Value<AudioSource> audioSource,
|
||||||
|
Value<YoutubeClientEngine> youtubeClientEngine,
|
||||||
Value<SourceCodecs> streamMusicCodec,
|
Value<SourceCodecs> streamMusicCodec,
|
||||||
Value<SourceCodecs> downloadMusicCodec,
|
Value<SourceCodecs> downloadMusicCodec,
|
||||||
Value<bool> discordPresence,
|
Value<bool> discordPresence,
|
||||||
@ -4595,6 +4651,7 @@ typedef $$PreferencesTableTableUpdateCompanionBuilder
|
|||||||
Value<String> invidiousInstance,
|
Value<String> invidiousInstance,
|
||||||
Value<ThemeMode> themeMode,
|
Value<ThemeMode> themeMode,
|
||||||
Value<AudioSource> audioSource,
|
Value<AudioSource> audioSource,
|
||||||
|
Value<YoutubeClientEngine> youtubeClientEngine,
|
||||||
Value<SourceCodecs> streamMusicCodec,
|
Value<SourceCodecs> streamMusicCodec,
|
||||||
Value<SourceCodecs> downloadMusicCodec,
|
Value<SourceCodecs> downloadMusicCodec,
|
||||||
Value<bool> discordPresence,
|
Value<bool> discordPresence,
|
||||||
@ -4702,6 +4759,12 @@ class $$PreferencesTableTableFilterComposer
|
|||||||
column: $table.audioSource,
|
column: $table.audioSource,
|
||||||
builder: (column) => ColumnWithTypeConverterFilters(column));
|
builder: (column) => ColumnWithTypeConverterFilters(column));
|
||||||
|
|
||||||
|
ColumnWithTypeConverterFilters<YoutubeClientEngine, YoutubeClientEngine,
|
||||||
|
String>
|
||||||
|
get youtubeClientEngine => $composableBuilder(
|
||||||
|
column: $table.youtubeClientEngine,
|
||||||
|
builder: (column) => ColumnWithTypeConverterFilters(column));
|
||||||
|
|
||||||
ColumnWithTypeConverterFilters<SourceCodecs, SourceCodecs, String>
|
ColumnWithTypeConverterFilters<SourceCodecs, SourceCodecs, String>
|
||||||
get streamMusicCodec => $composableBuilder(
|
get streamMusicCodec => $composableBuilder(
|
||||||
column: $table.streamMusicCodec,
|
column: $table.streamMusicCodec,
|
||||||
@ -4812,6 +4875,10 @@ class $$PreferencesTableTableOrderingComposer
|
|||||||
ColumnOrderings<String> get audioSource => $composableBuilder(
|
ColumnOrderings<String> get audioSource => $composableBuilder(
|
||||||
column: $table.audioSource, builder: (column) => ColumnOrderings(column));
|
column: $table.audioSource, builder: (column) => ColumnOrderings(column));
|
||||||
|
|
||||||
|
ColumnOrderings<String> get youtubeClientEngine => $composableBuilder(
|
||||||
|
column: $table.youtubeClientEngine,
|
||||||
|
builder: (column) => ColumnOrderings(column));
|
||||||
|
|
||||||
ColumnOrderings<String> get streamMusicCodec => $composableBuilder(
|
ColumnOrderings<String> get streamMusicCodec => $composableBuilder(
|
||||||
column: $table.streamMusicCodec,
|
column: $table.streamMusicCodec,
|
||||||
builder: (column) => ColumnOrderings(column));
|
builder: (column) => ColumnOrderings(column));
|
||||||
@ -4915,6 +4982,10 @@ class $$PreferencesTableTableAnnotationComposer
|
|||||||
$composableBuilder(
|
$composableBuilder(
|
||||||
column: $table.audioSource, builder: (column) => column);
|
column: $table.audioSource, builder: (column) => column);
|
||||||
|
|
||||||
|
GeneratedColumnWithTypeConverter<YoutubeClientEngine, String>
|
||||||
|
get youtubeClientEngine => $composableBuilder(
|
||||||
|
column: $table.youtubeClientEngine, builder: (column) => column);
|
||||||
|
|
||||||
GeneratedColumnWithTypeConverter<SourceCodecs, String> get streamMusicCodec =>
|
GeneratedColumnWithTypeConverter<SourceCodecs, String> get streamMusicCodec =>
|
||||||
$composableBuilder(
|
$composableBuilder(
|
||||||
column: $table.streamMusicCodec, builder: (column) => column);
|
column: $table.streamMusicCodec, builder: (column) => column);
|
||||||
@ -4985,6 +5056,8 @@ class $$PreferencesTableTableTableManager extends RootTableManager<
|
|||||||
Value<String> invidiousInstance = const Value.absent(),
|
Value<String> invidiousInstance = 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<YoutubeClientEngine> youtubeClientEngine =
|
||||||
|
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> discordPresence = const Value.absent(),
|
||||||
@ -5014,6 +5087,7 @@ class $$PreferencesTableTableTableManager extends RootTableManager<
|
|||||||
invidiousInstance: invidiousInstance,
|
invidiousInstance: invidiousInstance,
|
||||||
themeMode: themeMode,
|
themeMode: themeMode,
|
||||||
audioSource: audioSource,
|
audioSource: audioSource,
|
||||||
|
youtubeClientEngine: youtubeClientEngine,
|
||||||
streamMusicCodec: streamMusicCodec,
|
streamMusicCodec: streamMusicCodec,
|
||||||
downloadMusicCodec: downloadMusicCodec,
|
downloadMusicCodec: downloadMusicCodec,
|
||||||
discordPresence: discordPresence,
|
discordPresence: discordPresence,
|
||||||
@ -5043,6 +5117,8 @@ class $$PreferencesTableTableTableManager extends RootTableManager<
|
|||||||
Value<String> invidiousInstance = const Value.absent(),
|
Value<String> invidiousInstance = 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<YoutubeClientEngine> youtubeClientEngine =
|
||||||
|
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> discordPresence = const Value.absent(),
|
||||||
@ -5072,6 +5148,7 @@ class $$PreferencesTableTableTableManager extends RootTableManager<
|
|||||||
invidiousInstance: invidiousInstance,
|
invidiousInstance: invidiousInstance,
|
||||||
themeMode: themeMode,
|
themeMode: themeMode,
|
||||||
audioSource: audioSource,
|
audioSource: audioSource,
|
||||||
|
youtubeClientEngine: youtubeClientEngine,
|
||||||
streamMusicCodec: streamMusicCodec,
|
streamMusicCodec: streamMusicCodec,
|
||||||
downloadMusicCodec: downloadMusicCodec,
|
downloadMusicCodec: downloadMusicCodec,
|
||||||
discordPresence: discordPresence,
|
discordPresence: discordPresence,
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
// dart format width=80
|
// dart format width=80
|
||||||
import 'package:drift/internal/versioned_schema.dart' as i0;
|
import 'package:drift/internal/versioned_schema.dart' as i0;
|
||||||
import 'package:drift/drift.dart' as i1;
|
import 'package:drift/drift.dart' as i1;
|
||||||
import 'package:drift/drift.dart'; // ignore_for_file: type=lint,unused_import
|
import 'package:drift/drift.dart';
|
||||||
import 'package:shadcn_flutter/shadcn_flutter.dart';
|
import 'package:shadcn_flutter/shadcn_flutter.dart';
|
||||||
import 'package:spotify/spotify.dart';
|
import 'package:spotify/spotify.dart';
|
||||||
import 'package:spotube/models/database/database.dart';
|
import 'package:spotube/models/database/database.dart';
|
||||||
import 'package:spotube/services/sourced_track/enums.dart';
|
import 'package:spotube/services/sourced_track/enums.dart'; // ignore_for_file: type=lint,unused_import
|
||||||
|
|
||||||
// GENERATED BY drift_dev, DO NOT MODIFY.
|
// GENERATED BY drift_dev, DO NOT MODIFY.
|
||||||
final class Schema2 extends i0.VersionedSchema {
|
final class Schema2 extends i0.VersionedSchema {
|
||||||
@ -907,9 +907,291 @@ i1.GeneratedColumn<bool> _column_53(String aliasedName) =>
|
|||||||
defaultConstraints: i1.GeneratedColumn.constraintIsAlways(
|
defaultConstraints: i1.GeneratedColumn.constraintIsAlways(
|
||||||
'CHECK ("cache_music" IN (0, 1))'),
|
'CHECK ("cache_music" IN (0, 1))'),
|
||||||
defaultValue: const Constant(true));
|
defaultValue: const Constant(true));
|
||||||
|
|
||||||
|
final class Schema4 extends i0.VersionedSchema {
|
||||||
|
Schema4({required super.database}) : super(version: 4);
|
||||||
|
@override
|
||||||
|
late final List<i1.DatabaseSchemaEntity> entities = [
|
||||||
|
authenticationTable,
|
||||||
|
blacklistTable,
|
||||||
|
preferencesTable,
|
||||||
|
scrobblerTable,
|
||||||
|
skipSegmentTable,
|
||||||
|
sourceMatchTable,
|
||||||
|
audioPlayerStateTable,
|
||||||
|
playlistTable,
|
||||||
|
playlistMediaTable,
|
||||||
|
historyTable,
|
||||||
|
lyricsTable,
|
||||||
|
uniqueBlacklist,
|
||||||
|
uniqTrackMatch,
|
||||||
|
];
|
||||||
|
late final Shape0 authenticationTable = Shape0(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'authentication_table',
|
||||||
|
withoutRowId: false,
|
||||||
|
isStrict: false,
|
||||||
|
tableConstraints: [],
|
||||||
|
columns: [
|
||||||
|
_column_0,
|
||||||
|
_column_1,
|
||||||
|
_column_2,
|
||||||
|
_column_3,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null);
|
||||||
|
late final Shape1 blacklistTable = Shape1(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'blacklist_table',
|
||||||
|
withoutRowId: false,
|
||||||
|
isStrict: false,
|
||||||
|
tableConstraints: [],
|
||||||
|
columns: [
|
||||||
|
_column_0,
|
||||||
|
_column_4,
|
||||||
|
_column_5,
|
||||||
|
_column_6,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null);
|
||||||
|
late final Shape12 preferencesTable = Shape12(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'preferences_table',
|
||||||
|
withoutRowId: false,
|
||||||
|
isStrict: false,
|
||||||
|
tableConstraints: [],
|
||||||
|
columns: [
|
||||||
|
_column_0,
|
||||||
|
_column_7,
|
||||||
|
_column_8,
|
||||||
|
_column_9,
|
||||||
|
_column_10,
|
||||||
|
_column_11,
|
||||||
|
_column_12,
|
||||||
|
_column_13,
|
||||||
|
_column_14,
|
||||||
|
_column_15,
|
||||||
|
_column_16,
|
||||||
|
_column_17,
|
||||||
|
_column_18,
|
||||||
|
_column_19,
|
||||||
|
_column_20,
|
||||||
|
_column_21,
|
||||||
|
_column_22,
|
||||||
|
_column_23,
|
||||||
|
_column_24,
|
||||||
|
_column_25,
|
||||||
|
_column_26,
|
||||||
|
_column_54,
|
||||||
|
_column_27,
|
||||||
|
_column_28,
|
||||||
|
_column_29,
|
||||||
|
_column_30,
|
||||||
|
_column_31,
|
||||||
|
_column_53,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null);
|
||||||
|
late final Shape3 scrobblerTable = Shape3(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'scrobbler_table',
|
||||||
|
withoutRowId: false,
|
||||||
|
isStrict: false,
|
||||||
|
tableConstraints: [],
|
||||||
|
columns: [
|
||||||
|
_column_0,
|
||||||
|
_column_32,
|
||||||
|
_column_33,
|
||||||
|
_column_34,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null);
|
||||||
|
late final Shape4 skipSegmentTable = Shape4(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'skip_segment_table',
|
||||||
|
withoutRowId: false,
|
||||||
|
isStrict: false,
|
||||||
|
tableConstraints: [],
|
||||||
|
columns: [
|
||||||
|
_column_0,
|
||||||
|
_column_35,
|
||||||
|
_column_36,
|
||||||
|
_column_37,
|
||||||
|
_column_32,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null);
|
||||||
|
late final Shape5 sourceMatchTable = Shape5(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'source_match_table',
|
||||||
|
withoutRowId: false,
|
||||||
|
isStrict: false,
|
||||||
|
tableConstraints: [],
|
||||||
|
columns: [
|
||||||
|
_column_0,
|
||||||
|
_column_37,
|
||||||
|
_column_38,
|
||||||
|
_column_39,
|
||||||
|
_column_32,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null);
|
||||||
|
late final Shape6 audioPlayerStateTable = Shape6(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'audio_player_state_table',
|
||||||
|
withoutRowId: false,
|
||||||
|
isStrict: false,
|
||||||
|
tableConstraints: [],
|
||||||
|
columns: [
|
||||||
|
_column_0,
|
||||||
|
_column_40,
|
||||||
|
_column_41,
|
||||||
|
_column_42,
|
||||||
|
_column_43,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null);
|
||||||
|
late final Shape7 playlistTable = Shape7(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'playlist_table',
|
||||||
|
withoutRowId: false,
|
||||||
|
isStrict: false,
|
||||||
|
tableConstraints: [],
|
||||||
|
columns: [
|
||||||
|
_column_0,
|
||||||
|
_column_44,
|
||||||
|
_column_45,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null);
|
||||||
|
late final Shape8 playlistMediaTable = Shape8(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'playlist_media_table',
|
||||||
|
withoutRowId: false,
|
||||||
|
isStrict: false,
|
||||||
|
tableConstraints: [],
|
||||||
|
columns: [
|
||||||
|
_column_0,
|
||||||
|
_column_46,
|
||||||
|
_column_47,
|
||||||
|
_column_48,
|
||||||
|
_column_49,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null);
|
||||||
|
late final Shape9 historyTable = Shape9(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'history_table',
|
||||||
|
withoutRowId: false,
|
||||||
|
isStrict: false,
|
||||||
|
tableConstraints: [],
|
||||||
|
columns: [
|
||||||
|
_column_0,
|
||||||
|
_column_32,
|
||||||
|
_column_50,
|
||||||
|
_column_51,
|
||||||
|
_column_52,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null);
|
||||||
|
late final Shape10 lyricsTable = Shape10(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'lyrics_table',
|
||||||
|
withoutRowId: false,
|
||||||
|
isStrict: false,
|
||||||
|
tableConstraints: [],
|
||||||
|
columns: [
|
||||||
|
_column_0,
|
||||||
|
_column_37,
|
||||||
|
_column_52,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null);
|
||||||
|
final i1.Index uniqueBlacklist = i1.Index('unique_blacklist',
|
||||||
|
'CREATE UNIQUE INDEX unique_blacklist ON blacklist_table (element_type, element_id)');
|
||||||
|
final i1.Index uniqTrackMatch = i1.Index('uniq_track_match',
|
||||||
|
'CREATE UNIQUE INDEX uniq_track_match ON source_match_table (track_id, source_id, source_type)');
|
||||||
|
}
|
||||||
|
|
||||||
|
class Shape12 extends i0.VersionedTable {
|
||||||
|
Shape12({required super.source, required super.alias}) : super.aliased();
|
||||||
|
i1.GeneratedColumn<int> get id =>
|
||||||
|
columnsByName['id']! as i1.GeneratedColumn<int>;
|
||||||
|
i1.GeneratedColumn<String> get audioQuality =>
|
||||||
|
columnsByName['audio_quality']! as i1.GeneratedColumn<String>;
|
||||||
|
i1.GeneratedColumn<bool> get albumColorSync =>
|
||||||
|
columnsByName['album_color_sync']! as i1.GeneratedColumn<bool>;
|
||||||
|
i1.GeneratedColumn<bool> get amoledDarkTheme =>
|
||||||
|
columnsByName['amoled_dark_theme']! as i1.GeneratedColumn<bool>;
|
||||||
|
i1.GeneratedColumn<bool> get checkUpdate =>
|
||||||
|
columnsByName['check_update']! as i1.GeneratedColumn<bool>;
|
||||||
|
i1.GeneratedColumn<bool> get normalizeAudio =>
|
||||||
|
columnsByName['normalize_audio']! as i1.GeneratedColumn<bool>;
|
||||||
|
i1.GeneratedColumn<bool> get showSystemTrayIcon =>
|
||||||
|
columnsByName['show_system_tray_icon']! as i1.GeneratedColumn<bool>;
|
||||||
|
i1.GeneratedColumn<bool> get systemTitleBar =>
|
||||||
|
columnsByName['system_title_bar']! as i1.GeneratedColumn<bool>;
|
||||||
|
i1.GeneratedColumn<bool> get skipNonMusic =>
|
||||||
|
columnsByName['skip_non_music']! as i1.GeneratedColumn<bool>;
|
||||||
|
i1.GeneratedColumn<String> get closeBehavior =>
|
||||||
|
columnsByName['close_behavior']! as i1.GeneratedColumn<String>;
|
||||||
|
i1.GeneratedColumn<String> get accentColorScheme =>
|
||||||
|
columnsByName['accent_color_scheme']! as i1.GeneratedColumn<String>;
|
||||||
|
i1.GeneratedColumn<String> get layoutMode =>
|
||||||
|
columnsByName['layout_mode']! as i1.GeneratedColumn<String>;
|
||||||
|
i1.GeneratedColumn<String> get locale =>
|
||||||
|
columnsByName['locale']! as i1.GeneratedColumn<String>;
|
||||||
|
i1.GeneratedColumn<String> get market =>
|
||||||
|
columnsByName['market']! as i1.GeneratedColumn<String>;
|
||||||
|
i1.GeneratedColumn<String> get searchMode =>
|
||||||
|
columnsByName['search_mode']! as i1.GeneratedColumn<String>;
|
||||||
|
i1.GeneratedColumn<String> get downloadLocation =>
|
||||||
|
columnsByName['download_location']! as i1.GeneratedColumn<String>;
|
||||||
|
i1.GeneratedColumn<String> get localLibraryLocation =>
|
||||||
|
columnsByName['local_library_location']! as i1.GeneratedColumn<String>;
|
||||||
|
i1.GeneratedColumn<String> get pipedInstance =>
|
||||||
|
columnsByName['piped_instance']! as i1.GeneratedColumn<String>;
|
||||||
|
i1.GeneratedColumn<String> get invidiousInstance =>
|
||||||
|
columnsByName['invidious_instance']! as i1.GeneratedColumn<String>;
|
||||||
|
i1.GeneratedColumn<String> get themeMode =>
|
||||||
|
columnsByName['theme_mode']! as i1.GeneratedColumn<String>;
|
||||||
|
i1.GeneratedColumn<String> get audioSource =>
|
||||||
|
columnsByName['audio_source']! as i1.GeneratedColumn<String>;
|
||||||
|
i1.GeneratedColumn<String> get youtubeClientEngine =>
|
||||||
|
columnsByName['youtube_client_engine']! as i1.GeneratedColumn<String>;
|
||||||
|
i1.GeneratedColumn<String> get streamMusicCodec =>
|
||||||
|
columnsByName['stream_music_codec']! as i1.GeneratedColumn<String>;
|
||||||
|
i1.GeneratedColumn<String> get downloadMusicCodec =>
|
||||||
|
columnsByName['download_music_codec']! as i1.GeneratedColumn<String>;
|
||||||
|
i1.GeneratedColumn<bool> get discordPresence =>
|
||||||
|
columnsByName['discord_presence']! as i1.GeneratedColumn<bool>;
|
||||||
|
i1.GeneratedColumn<bool> get endlessPlayback =>
|
||||||
|
columnsByName['endless_playback']! as i1.GeneratedColumn<bool>;
|
||||||
|
i1.GeneratedColumn<bool> get enableConnect =>
|
||||||
|
columnsByName['enable_connect']! as i1.GeneratedColumn<bool>;
|
||||||
|
i1.GeneratedColumn<bool> get cacheMusic =>
|
||||||
|
columnsByName['cache_music']! as i1.GeneratedColumn<bool>;
|
||||||
|
}
|
||||||
|
|
||||||
|
i1.GeneratedColumn<String> _column_54(String aliasedName) =>
|
||||||
|
i1.GeneratedColumn<String>('youtube_client_engine', aliasedName, false,
|
||||||
|
type: i1.DriftSqlType.string,
|
||||||
|
defaultValue: Constant(YoutubeClientEngine.youtubeExplode.name));
|
||||||
i0.MigrationStepWithVersion migrationSteps({
|
i0.MigrationStepWithVersion migrationSteps({
|
||||||
required Future<void> Function(i1.Migrator m, Schema2 schema) from1To2,
|
required Future<void> Function(i1.Migrator m, Schema2 schema) from1To2,
|
||||||
required Future<void> Function(i1.Migrator m, Schema3 schema) from2To3,
|
required Future<void> Function(i1.Migrator m, Schema3 schema) from2To3,
|
||||||
|
required Future<void> Function(i1.Migrator m, Schema4 schema) from3To4,
|
||||||
}) {
|
}) {
|
||||||
return (currentVersion, database) async {
|
return (currentVersion, database) async {
|
||||||
switch (currentVersion) {
|
switch (currentVersion) {
|
||||||
@ -923,6 +1205,11 @@ i0.MigrationStepWithVersion migrationSteps({
|
|||||||
final migrator = i1.Migrator(database, schema);
|
final migrator = i1.Migrator(database, schema);
|
||||||
await from2To3(migrator, schema);
|
await from2To3(migrator, schema);
|
||||||
return 3;
|
return 3;
|
||||||
|
case 3:
|
||||||
|
final schema = Schema4(database: database);
|
||||||
|
final migrator = i1.Migrator(database, schema);
|
||||||
|
await from3To4(migrator, schema);
|
||||||
|
return 4;
|
||||||
default:
|
default:
|
||||||
throw ArgumentError.value('Unknown migration from $currentVersion');
|
throw ArgumentError.value('Unknown migration from $currentVersion');
|
||||||
}
|
}
|
||||||
@ -932,9 +1219,11 @@ i0.MigrationStepWithVersion migrationSteps({
|
|||||||
i1.OnUpgrade stepByStep({
|
i1.OnUpgrade stepByStep({
|
||||||
required Future<void> Function(i1.Migrator m, Schema2 schema) from1To2,
|
required Future<void> Function(i1.Migrator m, Schema2 schema) from1To2,
|
||||||
required Future<void> Function(i1.Migrator m, Schema3 schema) from2To3,
|
required Future<void> Function(i1.Migrator m, Schema3 schema) from2To3,
|
||||||
|
required Future<void> Function(i1.Migrator m, Schema4 schema) from3To4,
|
||||||
}) =>
|
}) =>
|
||||||
i0.VersionedSchema.stepByStepHelper(
|
i0.VersionedSchema.stepByStepHelper(
|
||||||
step: migrationSteps(
|
step: migrationSteps(
|
||||||
from1To2: from1To2,
|
from1To2: from1To2,
|
||||||
from2To3: from2To3,
|
from2To3: from2To3,
|
||||||
|
from3To4: from3To4,
|
||||||
));
|
));
|
||||||
|
@ -20,6 +20,26 @@ enum AudioSource {
|
|||||||
String get label => name[0].toUpperCase() + name.substring(1);
|
String get label => name[0].toUpperCase() + name.substring(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum YoutubeClientEngine {
|
||||||
|
ytDlp("yt-dlp"),
|
||||||
|
youtubeExplode("YouTubeExplode"),
|
||||||
|
newPipe("NewPipe");
|
||||||
|
|
||||||
|
final String label;
|
||||||
|
|
||||||
|
const YoutubeClientEngine(this.label);
|
||||||
|
|
||||||
|
bool isAvailableForPlatform() {
|
||||||
|
return switch (this) {
|
||||||
|
YoutubeClientEngine.youtubeExplode =>
|
||||||
|
YouTubeExplodeEngine.isAvailableForPlatform,
|
||||||
|
YoutubeClientEngine.ytDlp => YtDlpEngine.isAvailableForPlatform,
|
||||||
|
// TODO: Implement new pipe support
|
||||||
|
YoutubeClientEngine.newPipe => false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
enum MusicCodec {
|
enum MusicCodec {
|
||||||
m4a._("M4a (Best for downloaded music)"),
|
m4a._("M4a (Best for downloaded music)"),
|
||||||
weba._("WebA (Best for streamed music)\nDoesn't support audio metadata");
|
weba._("WebA (Best for streamed music)\nDoesn't support audio metadata");
|
||||||
@ -84,6 +104,8 @@ class PreferencesTable extends Table {
|
|||||||
textEnum<ThemeMode>().withDefault(Constant(ThemeMode.system.name))();
|
textEnum<ThemeMode>().withDefault(Constant(ThemeMode.system.name))();
|
||||||
TextColumn get audioSource =>
|
TextColumn get audioSource =>
|
||||||
textEnum<AudioSource>().withDefault(Constant(AudioSource.youtube.name))();
|
textEnum<AudioSource>().withDefault(Constant(AudioSource.youtube.name))();
|
||||||
|
TextColumn get youtubeClientEngine => textEnum<YoutubeClientEngine>()
|
||||||
|
.withDefault(Constant(YoutubeClientEngine.youtubeExplode.name))();
|
||||||
TextColumn get streamMusicCodec =>
|
TextColumn get streamMusicCodec =>
|
||||||
textEnum<SourceCodecs>().withDefault(Constant(SourceCodecs.weba.name))();
|
textEnum<SourceCodecs>().withDefault(Constant(SourceCodecs.weba.name))();
|
||||||
TextColumn get downloadMusicCodec =>
|
TextColumn get downloadMusicCodec =>
|
||||||
@ -120,6 +142,7 @@ class PreferencesTable extends Table {
|
|||||||
invidiousInstance: "https://inv.nadeko.net",
|
invidiousInstance: "https://inv.nadeko.net",
|
||||||
themeMode: ThemeMode.system,
|
themeMode: ThemeMode.system,
|
||||||
audioSource: AudioSource.youtube,
|
audioSource: AudioSource.youtube,
|
||||||
|
youtubeClientEngine: YoutubeClientEngine.youtubeExplode,
|
||||||
streamMusicCodec: SourceCodecs.m4a,
|
streamMusicCodec: SourceCodecs.m4a,
|
||||||
downloadMusicCodec: SourceCodecs.m4a,
|
downloadMusicCodec: SourceCodecs.m4a,
|
||||||
discordPresence: true,
|
discordPresence: true,
|
||||||
|
@ -19,6 +19,7 @@ import 'package:spotube/provider/audio_player/audio_player.dart';
|
|||||||
import 'package:spotube/provider/audio_player/querying_track_info.dart';
|
import 'package:spotube/provider/audio_player/querying_track_info.dart';
|
||||||
import 'package:spotube/provider/server/active_sourced_track.dart';
|
import 'package:spotube/provider/server/active_sourced_track.dart';
|
||||||
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
|
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
|
||||||
|
import 'package:spotube/provider/youtube_engine/youtube_engine.dart';
|
||||||
import 'package:spotube/services/sourced_track/models/source_info.dart';
|
import 'package:spotube/services/sourced_track/models/source_info.dart';
|
||||||
import 'package:spotube/services/sourced_track/models/video_info.dart';
|
import 'package:spotube/services/sourced_track/models/video_info.dart';
|
||||||
import 'package:spotube/services/sourced_track/sourced_track.dart';
|
import 'package:spotube/services/sourced_track/sourced_track.dart';
|
||||||
@ -68,6 +69,7 @@ class SiblingTracksSheet extends HookConsumerWidget {
|
|||||||
final playlist = ref.watch(audioPlayerProvider);
|
final playlist = ref.watch(audioPlayerProvider);
|
||||||
final isFetchingActiveTrack = ref.watch(queryingTrackInfoProvider);
|
final isFetchingActiveTrack = ref.watch(queryingTrackInfoProvider);
|
||||||
final preferences = ref.watch(userPreferencesProvider);
|
final preferences = ref.watch(userPreferencesProvider);
|
||||||
|
final youtubeEngine = ref.watch(youtubeEngineProvider);
|
||||||
|
|
||||||
final isSearching = useState(false);
|
final isSearching = useState(false);
|
||||||
final searchMode = useState(preferences.searchMode);
|
final searchMode = useState(preferences.searchMode);
|
||||||
@ -115,14 +117,14 @@ class SiblingTracksSheet extends HookConsumerWidget {
|
|||||||
activeSourceInfo,
|
activeSourceInfo,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
final resultsYt = await youtubeClient.search.search(searchTerm.trim());
|
final resultsYt = await youtubeEngine.searchVideos(searchTerm.trim());
|
||||||
|
|
||||||
final searchResults = await Future.wait(
|
final searchResults = await Future.wait(
|
||||||
resultsYt
|
resultsYt
|
||||||
.map(YoutubeVideoInfo.fromVideo)
|
.map(YoutubeVideoInfo.fromVideo)
|
||||||
.mapIndexed((i, video) async {
|
.mapIndexed((i, video) async {
|
||||||
final siblingType =
|
final siblingType =
|
||||||
await YoutubeSourcedTrack.toSiblingType(i, video);
|
await YoutubeSourcedTrack.toSiblingType(i, video, ref);
|
||||||
return siblingType.info;
|
return siblingType.info;
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
@ -139,6 +141,7 @@ class SiblingTracksSheet extends HookConsumerWidget {
|
|||||||
searchMode.value,
|
searchMode.value,
|
||||||
activeTrack,
|
activeTrack,
|
||||||
preferences.audioSource,
|
preferences.audioSource,
|
||||||
|
youtubeEngine,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
final siblings = useMemoized(
|
final siblings = useMemoized(
|
||||||
@ -151,12 +154,15 @@ class SiblingTracksSheet extends HookConsumerWidget {
|
|||||||
[activeTrack, isFetchingActiveTrack],
|
[activeTrack, isFetchingActiveTrack],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
final previousActiveTrack = usePrevious(activeTrack);
|
||||||
useEffect(() {
|
useEffect(() {
|
||||||
|
/// Populate sibling when active track changes
|
||||||
|
if (previousActiveTrack?.id == activeTrack?.id) return;
|
||||||
if (activeTrack is SourcedTrack && activeTrack.siblings.isEmpty) {
|
if (activeTrack is SourcedTrack && activeTrack.siblings.isEmpty) {
|
||||||
activeTrackNotifier.populateSibling();
|
activeTrackNotifier.populateSibling();
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}, [activeTrack]);
|
}, [activeTrack, previousActiveTrack]);
|
||||||
|
|
||||||
final itemBuilder = useCallback(
|
final itemBuilder = useCallback(
|
||||||
(SourceInfo sourceInfo) {
|
(SourceInfo sourceInfo) {
|
||||||
|
@ -0,0 +1,65 @@
|
|||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:shadcn_flutter/shadcn_flutter.dart';
|
||||||
|
import 'package:spotube/collections/spotube_icons.dart';
|
||||||
|
import 'package:spotube/extensions/context.dart';
|
||||||
|
import 'package:spotube/models/database/database.dart';
|
||||||
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
|
|
||||||
|
const engineDownloadUrls = {
|
||||||
|
YoutubeClientEngine.ytDlp:
|
||||||
|
"https://github.com/yt-dlp/yt-dlp?tab=readme-ov-file#installation",
|
||||||
|
};
|
||||||
|
|
||||||
|
class YouTubeEngineNotInstalledDialog extends HookConsumerWidget {
|
||||||
|
final YoutubeClientEngine engine;
|
||||||
|
const YouTubeEngineNotInstalledDialog({
|
||||||
|
super.key,
|
||||||
|
required this.engine,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, ref) {
|
||||||
|
return AlertDialog(
|
||||||
|
title: Row(
|
||||||
|
spacing: 8,
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
const Icon(SpotubeIcons.error, color: Colors.red),
|
||||||
|
Text(
|
||||||
|
context.l10n.youtube_engine_not_installed_title(engine.label),
|
||||||
|
style: const TextStyle(color: Colors.red),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
content: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
spacing: 10,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
context.l10n.youtube_engine_not_installed_message(engine.label),
|
||||||
|
),
|
||||||
|
if (engineDownloadUrls[engine] != null)
|
||||||
|
Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Text("${context.l10n.download}:"),
|
||||||
|
Button.link(
|
||||||
|
child: Text(engineDownloadUrls[engine]!.split("?").first),
|
||||||
|
onPressed: () async {
|
||||||
|
launchUrl(Uri.parse(engineDownloadUrls[engine]!));
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
Button.secondary(
|
||||||
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
|
child: Text(context.l10n.ok),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -3,6 +3,7 @@ import 'package:flutter/services.dart';
|
|||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:shadcn_flutter/shadcn_flutter.dart';
|
import 'package:shadcn_flutter/shadcn_flutter.dart';
|
||||||
|
import 'package:spotube/hooks/configurators/use_check_yt_dlp_installed.dart';
|
||||||
import 'package:spotube/modules/root/bottom_player.dart';
|
import 'package:spotube/modules/root/bottom_player.dart';
|
||||||
import 'package:spotube/modules/root/sidebar/sidebar.dart';
|
import 'package:spotube/modules/root/sidebar/sidebar.dart';
|
||||||
import 'package:spotube/modules/root/spotube_navigation_bar.dart';
|
import 'package:spotube/modules/root/spotube_navigation_bar.dart';
|
||||||
@ -21,9 +22,11 @@ class RootAppPage extends HookConsumerWidget {
|
|||||||
final brightness = Theme.of(context).brightness;
|
final brightness = Theme.of(context).brightness;
|
||||||
|
|
||||||
ref.listen(glanceProvider, (_, __) {});
|
ref.listen(glanceProvider, (_, __) {});
|
||||||
|
|
||||||
useGlobalSubscriptions(ref);
|
useGlobalSubscriptions(ref);
|
||||||
useDownloaderDialogs(ref);
|
useDownloaderDialogs(ref);
|
||||||
useEndlessPlayback(ref);
|
useEndlessPlayback(ref);
|
||||||
|
useCheckYtDlpInstalled(ref);
|
||||||
|
|
||||||
useEffect(() {
|
useEffect(() {
|
||||||
SystemChrome.setSystemUIOverlayStyle(
|
SystemChrome.setSystemUIOverlayStyle(
|
||||||
|
@ -13,11 +13,13 @@ import 'package:spotube/models/database/database.dart';
|
|||||||
import 'package:spotube/modules/settings/section_card_with_heading.dart';
|
import 'package:spotube/modules/settings/section_card_with_heading.dart';
|
||||||
import 'package:spotube/components/adaptive/adaptive_select_tile.dart';
|
import 'package:spotube/components/adaptive/adaptive_select_tile.dart';
|
||||||
import 'package:spotube/extensions/context.dart';
|
import 'package:spotube/extensions/context.dart';
|
||||||
|
import 'package:spotube/modules/settings/youtube_engine_not_installed_dialog.dart';
|
||||||
import 'package:spotube/provider/audio_player/sources/invidious_instances_provider.dart';
|
import 'package:spotube/provider/audio_player/sources/invidious_instances_provider.dart';
|
||||||
import 'package:spotube/provider/audio_player/sources/piped_instances_provider.dart';
|
import 'package:spotube/provider/audio_player/sources/piped_instances_provider.dart';
|
||||||
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
|
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
|
||||||
|
|
||||||
import 'package:spotube/services/sourced_track/enums.dart';
|
import 'package:spotube/services/sourced_track/enums.dart';
|
||||||
|
import 'package:spotube/services/youtube_engine/yt_dlp_engine.dart';
|
||||||
import 'package:spotube/utils/platform.dart';
|
import 'package:spotube/utils/platform.dart';
|
||||||
|
|
||||||
class SettingsPlaybackSection extends HookConsumerWidget {
|
class SettingsPlaybackSection extends HookConsumerWidget {
|
||||||
@ -195,13 +197,36 @@ class SettingsPlaybackSection extends HookConsumerWidget {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
AnimatedCrossFade(
|
switch (preferences.audioSource) {
|
||||||
duration: const Duration(milliseconds: 300),
|
AudioSource.youtube => AdaptiveSelectTile<YoutubeClientEngine>(
|
||||||
crossFadeState: preferences.audioSource == AudioSource.youtube
|
secondary: const Icon(SpotubeIcons.engine),
|
||||||
? CrossFadeState.showFirst
|
title: Text(context.l10n.youtube_engine),
|
||||||
: CrossFadeState.showSecond,
|
value: preferences.youtubeClientEngine,
|
||||||
firstChild: const SizedBox.shrink(),
|
options: YoutubeClientEngine.values
|
||||||
secondChild: AdaptiveSelectTile<SearchMode>(
|
.where((e) => e.isAvailableForPlatform())
|
||||||
|
.map((e) => SelectItemButton(
|
||||||
|
value: e,
|
||||||
|
child: Text(e.label),
|
||||||
|
))
|
||||||
|
.toList(),
|
||||||
|
onChanged: (value) async {
|
||||||
|
if (value == null) return;
|
||||||
|
if (value == YoutubeClientEngine.ytDlp &&
|
||||||
|
!await YtDlpEngine.isInstalled() &&
|
||||||
|
context.mounted) {
|
||||||
|
await showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) =>
|
||||||
|
YouTubeEngineNotInstalledDialog(engine: value),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
preferencesNotifier.setYoutubeClientEngine(value);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
AudioSource.piped ||
|
||||||
|
AudioSource.invidious =>
|
||||||
|
AdaptiveSelectTile<SearchMode>(
|
||||||
secondary: const Icon(SpotubeIcons.search),
|
secondary: const Icon(SpotubeIcons.search),
|
||||||
title: Text(context.l10n.search_mode),
|
title: Text(context.l10n.search_mode),
|
||||||
value: preferences.searchMode,
|
value: preferences.searchMode,
|
||||||
@ -216,7 +241,8 @@ class SettingsPlaybackSection extends HookConsumerWidget {
|
|||||||
preferencesNotifier.setSearchMode(value);
|
preferencesNotifier.setSearchMode(value);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
_ => const SizedBox.shrink(),
|
||||||
|
},
|
||||||
AnimatedCrossFade(
|
AnimatedCrossFade(
|
||||||
duration: const Duration(milliseconds: 300),
|
duration: const Duration(milliseconds: 300),
|
||||||
crossFadeState: preferences.searchMode == SearchMode.youtube &&
|
crossFadeState: preferences.searchMode == SearchMode.youtube &&
|
||||||
|
@ -207,6 +207,10 @@ class UserPreferencesNotifier extends Notifier<PreferencesTableData> {
|
|||||||
setData(PreferencesTableCompanion(audioSource: Value(type)));
|
setData(PreferencesTableCompanion(audioSource: Value(type)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void setYoutubeClientEngine(YoutubeClientEngine engine) {
|
||||||
|
setData(PreferencesTableCompanion(youtubeClientEngine: Value(engine)));
|
||||||
|
}
|
||||||
|
|
||||||
void setSystemTitleBar(bool isSystemTitleBar) {
|
void setSystemTitleBar(bool isSystemTitleBar) {
|
||||||
setData(
|
setData(
|
||||||
PreferencesTableCompanion(
|
PreferencesTableCompanion(
|
||||||
|
20
lib/provider/youtube_engine/youtube_engine.dart
Normal file
20
lib/provider/youtube_engine/youtube_engine.dart
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:spotube/models/database/database.dart';
|
||||||
|
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
|
||||||
|
import 'package:spotube/services/youtube_engine/youtube_explode_engine.dart';
|
||||||
|
import 'package:spotube/services/youtube_engine/yt_dlp_engine.dart';
|
||||||
|
|
||||||
|
final youtubeEngineProvider = Provider((ref) {
|
||||||
|
final engineMode = ref.watch(
|
||||||
|
userPreferencesProvider.select((value) => value.youtubeClientEngine),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (engineMode == YoutubeClientEngine.newPipe) {
|
||||||
|
throw UnimplementedError();
|
||||||
|
} else if (engineMode == YoutubeClientEngine.ytDlp &&
|
||||||
|
YtDlpEngine.isAvailableForPlatform) {
|
||||||
|
return YtDlpEngine();
|
||||||
|
} else {
|
||||||
|
return YouTubeExplodeEngine();
|
||||||
|
}
|
||||||
|
});
|
@ -4,6 +4,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|||||||
import 'package:spotify/spotify.dart';
|
import 'package:spotify/spotify.dart';
|
||||||
import 'package:spotube/models/database/database.dart';
|
import 'package:spotube/models/database/database.dart';
|
||||||
import 'package:spotube/provider/database/database.dart';
|
import 'package:spotube/provider/database/database.dart';
|
||||||
|
import 'package:spotube/provider/youtube_engine/youtube_engine.dart';
|
||||||
import 'package:spotube/services/logger/logger.dart';
|
import 'package:spotube/services/logger/logger.dart';
|
||||||
import 'package:spotube/services/song_link/song_link.dart';
|
import 'package:spotube/services/song_link/song_link.dart';
|
||||||
import 'package:spotube/services/sourced_track/enums.dart';
|
import 'package:spotube/services/sourced_track/enums.dart';
|
||||||
@ -15,7 +16,6 @@ import 'package:spotube/services/sourced_track/sourced_track.dart';
|
|||||||
import 'package:spotube/utils/service_utils.dart';
|
import 'package:spotube/utils/service_utils.dart';
|
||||||
import 'package:youtube_explode_dart/youtube_explode_dart.dart';
|
import 'package:youtube_explode_dart/youtube_explode_dart.dart';
|
||||||
|
|
||||||
final youtubeClient = YoutubeExplode();
|
|
||||||
final officialMusicRegex = RegExp(
|
final officialMusicRegex = RegExp(
|
||||||
r"official\s(video|audio|music\svideo|lyric\svideo|visualizer)",
|
r"official\s(video|audio|music\svideo|lyric\svideo|visualizer)",
|
||||||
caseSensitive: false,
|
caseSensitive: false,
|
||||||
@ -43,24 +43,15 @@ class YoutubeSourcedTrack extends SourcedTrack {
|
|||||||
required super.ref,
|
required super.ref,
|
||||||
});
|
});
|
||||||
|
|
||||||
static Future<StreamManifest> _getStreamManifest(String id) async {
|
|
||||||
return youtubeClient.videos.streamsClient.getManifest(
|
|
||||||
id,
|
|
||||||
requireWatchPage: false,
|
|
||||||
ytClients: [
|
|
||||||
YoutubeApiClient.android,
|
|
||||||
YoutubeApiClient.mweb,
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
static Future<YoutubeSourcedTrack> fetchFromTrack({
|
static Future<YoutubeSourcedTrack> fetchFromTrack({
|
||||||
required Track track,
|
required Track track,
|
||||||
required Ref ref,
|
required Ref ref,
|
||||||
}) async {
|
}) async {
|
||||||
// Indicates the track is requesting a stream refresh
|
// Indicates the track is requesting a stream refresh
|
||||||
if (track is YoutubeSourcedTrack) {
|
if (track is YoutubeSourcedTrack) {
|
||||||
final manifest = await _getStreamManifest(track.sourceInfo.id);
|
final manifest = await ref
|
||||||
|
.read(youtubeEngineProvider)
|
||||||
|
.getStreamManifest(track.sourceInfo.id);
|
||||||
|
|
||||||
final sourcedTrack = YoutubeSourcedTrack(
|
final sourcedTrack = YoutubeSourcedTrack(
|
||||||
ref: ref,
|
ref: ref,
|
||||||
@ -108,8 +99,10 @@ class YoutubeSourcedTrack extends SourcedTrack {
|
|||||||
track: track,
|
track: track,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
final item = await youtubeClient.videos.get(cachedSource.sourceId);
|
final (item, manifest) = await ref
|
||||||
final manifest = await _getStreamManifest(cachedSource.sourceId);
|
.read(youtubeEngineProvider)
|
||||||
|
.getVideoWithStreamInfo(cachedSource.sourceId);
|
||||||
|
|
||||||
final sourcedTrack = YoutubeSourcedTrack(
|
final sourcedTrack = YoutubeSourcedTrack(
|
||||||
ref: ref,
|
ref: ref,
|
||||||
siblings: [],
|
siblings: [],
|
||||||
@ -162,10 +155,13 @@ class YoutubeSourcedTrack extends SourcedTrack {
|
|||||||
static Future<SiblingType> toSiblingType(
|
static Future<SiblingType> toSiblingType(
|
||||||
int index,
|
int index,
|
||||||
YoutubeVideoInfo item,
|
YoutubeVideoInfo item,
|
||||||
|
dynamic ref,
|
||||||
) async {
|
) async {
|
||||||
|
assert(ref is WidgetRef || ref is Ref, "Invalid ref type");
|
||||||
SourceMap? sourceMap;
|
SourceMap? sourceMap;
|
||||||
if (index == 0) {
|
if (index == 0) {
|
||||||
final manifest = await _getStreamManifest(item.id);
|
final manifest =
|
||||||
|
await ref.read(youtubeEngineProvider).getStreamManifest(item.id);
|
||||||
sourceMap = toSourceMap(manifest);
|
sourceMap = toSourceMap(manifest);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -188,11 +184,8 @@ class YoutubeSourcedTrack extends SourcedTrack {
|
|||||||
|
|
||||||
static List<YoutubeVideoInfo> rankResults(
|
static List<YoutubeVideoInfo> rankResults(
|
||||||
List<YoutubeVideoInfo> results, Track track) {
|
List<YoutubeVideoInfo> results, Track track) {
|
||||||
final artists = (track.artists ?? [])
|
final artists =
|
||||||
.map((ar) => ar.name)
|
(track.artists ?? []).map((ar) => ar.name).toList().nonNulls.toList();
|
||||||
.toList()
|
|
||||||
.whereNotNull()
|
|
||||||
.toList();
|
|
||||||
|
|
||||||
return results
|
return results
|
||||||
.sorted((a, b) => b.views.compareTo(a.views))
|
.sorted((a, b) => b.views.compareTo(a.views))
|
||||||
@ -259,8 +252,9 @@ class YoutubeSourcedTrack extends SourcedTrack {
|
|||||||
await toSiblingType(
|
await toSiblingType(
|
||||||
0,
|
0,
|
||||||
YoutubeVideoInfo.fromVideo(
|
YoutubeVideoInfo.fromVideo(
|
||||||
await youtubeClient.videos.get(ytLink!.url!),
|
await ref.read(youtubeEngineProvider).getVideo(ytLink!.url!),
|
||||||
),
|
),
|
||||||
|
ref,
|
||||||
)
|
)
|
||||||
];
|
];
|
||||||
} on VideoUnplayableException catch (e, stack) {
|
} on VideoUnplayableException catch (e, stack) {
|
||||||
@ -271,15 +265,13 @@ class YoutubeSourcedTrack extends SourcedTrack {
|
|||||||
|
|
||||||
final query = SourcedTrack.getSearchTerm(track);
|
final query = SourcedTrack.getSearchTerm(track);
|
||||||
|
|
||||||
final searchResults = await youtubeClient.search.search(
|
final searchResults =
|
||||||
"$query - Topic",
|
await ref.read(youtubeEngineProvider).searchVideos("$query - Topic");
|
||||||
filter: TypeFilters.video,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (ServiceUtils.onlyContainsEnglish(query)) {
|
if (ServiceUtils.onlyContainsEnglish(query)) {
|
||||||
return await Future.wait(searchResults
|
return await Future.wait(searchResults
|
||||||
.map(YoutubeVideoInfo.fromVideo)
|
.map(YoutubeVideoInfo.fromVideo)
|
||||||
.mapIndexed(toSiblingType));
|
.mapIndexed((index, info) => toSiblingType(index, info, ref)));
|
||||||
}
|
}
|
||||||
|
|
||||||
final rankedSiblings = rankResults(
|
final rankedSiblings = rankResults(
|
||||||
@ -287,7 +279,10 @@ class YoutubeSourcedTrack extends SourcedTrack {
|
|||||||
track,
|
track,
|
||||||
);
|
);
|
||||||
|
|
||||||
return await Future.wait(rankedSiblings.mapIndexed(toSiblingType));
|
return await Future.wait(
|
||||||
|
rankedSiblings
|
||||||
|
.mapIndexed((index, info) => toSiblingType(index, info, ref)),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -305,7 +300,9 @@ class YoutubeSourcedTrack extends SourcedTrack {
|
|||||||
final newSiblings = siblings.where((s) => s.id != sibling.id).toList()
|
final newSiblings = siblings.where((s) => s.id != sibling.id).toList()
|
||||||
..insert(0, sourceInfo);
|
..insert(0, sourceInfo);
|
||||||
|
|
||||||
final manifest = await _getStreamManifest(newSourceInfo.id);
|
final manifest = await ref
|
||||||
|
.read(youtubeEngineProvider)
|
||||||
|
.getStreamManifest(newSourceInfo.id);
|
||||||
|
|
||||||
final database = ref.read(databaseProvider);
|
final database = ref.read(databaseProvider);
|
||||||
|
|
||||||
|
14
lib/services/youtube_engine/youtube_engine.dart
Normal file
14
lib/services/youtube_engine/youtube_engine.dart
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import 'package:youtube_explode_dart/youtube_explode_dart.dart';
|
||||||
|
|
||||||
|
abstract interface class YouTubeEngine {
|
||||||
|
static bool get isAvailableForPlatform => false;
|
||||||
|
|
||||||
|
static Future<bool> isInstalled() async {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Video> getVideo(String videoId);
|
||||||
|
Future<StreamManifest> getStreamManifest(String videoId);
|
||||||
|
Future<(Video, StreamManifest)> getVideoWithStreamInfo(String videoId);
|
||||||
|
Future<List<Video>> searchVideos(String query);
|
||||||
|
}
|
47
lib/services/youtube_engine/youtube_explode_engine.dart
Normal file
47
lib/services/youtube_engine/youtube_explode_engine.dart
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
import 'package:spotube/services/youtube_engine/youtube_engine.dart';
|
||||||
|
import 'package:youtube_explode_dart/youtube_explode_dart.dart';
|
||||||
|
|
||||||
|
class YouTubeExplodeEngine implements YouTubeEngine {
|
||||||
|
static final YoutubeExplode _youtubeExplode = YoutubeExplode();
|
||||||
|
|
||||||
|
static bool get isAvailableForPlatform => true;
|
||||||
|
|
||||||
|
static Future<bool> isInstalled() async {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<StreamManifest> getStreamManifest(String videoId) {
|
||||||
|
return _youtubeExplode.videos.streamsClient.getManifest(
|
||||||
|
videoId,
|
||||||
|
requireWatchPage: false,
|
||||||
|
ytClients: [
|
||||||
|
YoutubeApiClient.android,
|
||||||
|
YoutubeApiClient.mweb,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Video> getVideo(String videoId) {
|
||||||
|
return _youtubeExplode.videos.get(videoId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<(Video, StreamManifest)> getVideoWithStreamInfo(String videoId) async {
|
||||||
|
final video = await getVideo(videoId);
|
||||||
|
final streamManifest = await getStreamManifest(videoId);
|
||||||
|
|
||||||
|
return (video, streamManifest);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<List<Video>> searchVideos(String query) {
|
||||||
|
return _youtubeExplode.search
|
||||||
|
.search(
|
||||||
|
query,
|
||||||
|
filter: TypeFilters.video,
|
||||||
|
)
|
||||||
|
.then((searchList) => searchList.toList());
|
||||||
|
}
|
||||||
|
}
|
149
lib/services/youtube_engine/yt_dlp_engine.dart
Normal file
149
lib/services/youtube_engine/yt_dlp_engine.dart
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:collection/collection.dart';
|
||||||
|
import 'package:spotube/services/youtube_engine/youtube_engine.dart';
|
||||||
|
import 'package:spotube/utils/platform.dart';
|
||||||
|
import 'package:youtube_explode_dart/youtube_explode_dart.dart';
|
||||||
|
import 'package:yt_dlp_dart/yt_dlp_dart.dart';
|
||||||
|
// ignore: depend_on_referenced_packages
|
||||||
|
import 'package:http_parser/http_parser.dart';
|
||||||
|
|
||||||
|
class YtDlpEngine implements YouTubeEngine {
|
||||||
|
StreamManifest _parseFormats(List formats, videoId) {
|
||||||
|
final audioOnlyStreams = formats
|
||||||
|
.where(
|
||||||
|
(f) => f["resolution"] == "audio only" && f["manifest_url"] == null,
|
||||||
|
)
|
||||||
|
.sorted((a, b) => a["quality"] > b["quality"] ? 1 : -1)
|
||||||
|
.map((f) {
|
||||||
|
final filesize = f["filesize"] ?? f["filesize_approx"];
|
||||||
|
return AudioOnlyStreamInfo(
|
||||||
|
VideoId(videoId),
|
||||||
|
0,
|
||||||
|
Uri.parse(f["url"]),
|
||||||
|
StreamContainer.parse(
|
||||||
|
f["container"]?.replaceAll("_dash", "").replaceAll("m4a", "mp4"),
|
||||||
|
),
|
||||||
|
filesize != null ? FileSize(filesize) : FileSize.unknown,
|
||||||
|
Bitrate(
|
||||||
|
(((f["abr"] ?? f["tbr"] ?? 0) * 1000) as num).toInt(),
|
||||||
|
),
|
||||||
|
f["acodec"] ?? "webm",
|
||||||
|
f["format_note"],
|
||||||
|
[],
|
||||||
|
MediaType.parse(
|
||||||
|
"audio/${f["audio_ext"]}",
|
||||||
|
),
|
||||||
|
null,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
return StreamManifest(audioOnlyStreams);
|
||||||
|
}
|
||||||
|
|
||||||
|
Video _parseInfo(Map<String, dynamic> info) {
|
||||||
|
final publishDate = info["upload_date"] != null
|
||||||
|
? DateTime.fromMillisecondsSinceEpoch(
|
||||||
|
int.parse(info["upload_date"]) * 1000,
|
||||||
|
)
|
||||||
|
: DateTime.now();
|
||||||
|
return Video(
|
||||||
|
VideoId(info["id"]),
|
||||||
|
info["title"],
|
||||||
|
info["channel"],
|
||||||
|
ChannelId(info["channel_id"]),
|
||||||
|
publishDate,
|
||||||
|
info["upload_date"] as String? ?? DateTime.now().toString(),
|
||||||
|
publishDate,
|
||||||
|
info["description"] ?? "",
|
||||||
|
Duration(seconds: (info["duration"] as num).toInt()),
|
||||||
|
ThumbnailSet(info["id"]),
|
||||||
|
info["tags"]?.cast<String>() ?? <String>[],
|
||||||
|
Engagement(
|
||||||
|
info["view_count"],
|
||||||
|
info["like_count"],
|
||||||
|
null,
|
||||||
|
),
|
||||||
|
info["is_live"] ?? false,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool get isAvailableForPlatform => kIsDesktop;
|
||||||
|
|
||||||
|
static Future<bool> isInstalled() async {
|
||||||
|
return isAvailableForPlatform &&
|
||||||
|
await YtDlp.instance.checkAvailableInPath();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<StreamManifest> getStreamManifest(String videoId) async {
|
||||||
|
final formats = await YtDlp.instance.extractInfo(
|
||||||
|
"https://www.youtube.com/watch?v=$videoId",
|
||||||
|
formatSpecifiers: "%(formats)j",
|
||||||
|
extraArgs: [
|
||||||
|
"--no-check-certificate",
|
||||||
|
"--geo-bypass",
|
||||||
|
"--quiet",
|
||||||
|
"--ignore-errors"
|
||||||
|
],
|
||||||
|
) as List;
|
||||||
|
|
||||||
|
return _parseFormats(formats, videoId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Video> getVideo(String videoId) async {
|
||||||
|
final info = await YtDlp.instance.extractInfo(
|
||||||
|
"https://www.youtube.com/watch?v=$videoId",
|
||||||
|
formatSpecifiers: "%()j",
|
||||||
|
extraArgs: [
|
||||||
|
"--skip-download",
|
||||||
|
"--no-check-certificate",
|
||||||
|
"--geo-bypass",
|
||||||
|
"--quiet",
|
||||||
|
"--ignore-errors",
|
||||||
|
],
|
||||||
|
) as Map<String, dynamic>;
|
||||||
|
|
||||||
|
return _parseInfo(info);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<(Video, StreamManifest)> getVideoWithStreamInfo(String videoId) async {
|
||||||
|
final info = await YtDlp.instance.extractInfo(
|
||||||
|
"https://www.youtube.com/watch?v=$videoId",
|
||||||
|
formatSpecifiers: "%()j",
|
||||||
|
extraArgs: [
|
||||||
|
"--no-check-certificate",
|
||||||
|
"--geo-bypass",
|
||||||
|
"--quiet",
|
||||||
|
"--ignore-errors",
|
||||||
|
],
|
||||||
|
) as Map<String, dynamic>;
|
||||||
|
|
||||||
|
return (_parseInfo(info), _parseFormats(info["formats"], videoId));
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<List<Video>> searchVideos(String query) async {
|
||||||
|
final stdout = await YtDlp.instance.extractInfoString(
|
||||||
|
"ytsearch10:$query",
|
||||||
|
formatSpecifiers: "%()j",
|
||||||
|
extraArgs: [
|
||||||
|
"--skip-download",
|
||||||
|
"--no-check-certificate",
|
||||||
|
"--geo-bypass",
|
||||||
|
"--quiet",
|
||||||
|
"--ignore-errors",
|
||||||
|
"--flat-playlist",
|
||||||
|
"--no-playlist",
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
final json = jsonDecode(
|
||||||
|
"[${stdout.split("\n").where((s) => s.trim().isNotEmpty).join(",")}]",
|
||||||
|
) as List;
|
||||||
|
|
||||||
|
return json.map((e) => _parseInfo(e)).toList();
|
||||||
|
}
|
||||||
|
}
|
@ -2734,6 +2734,13 @@ packages:
|
|||||||
url: "https://github.com/Hexer10/youtube_explode_dart.git"
|
url: "https://github.com/Hexer10/youtube_explode_dart.git"
|
||||||
source: git
|
source: git
|
||||||
version: "2.3.7"
|
version: "2.3.7"
|
||||||
|
yt_dlp_dart:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
path: "../yt_dlp_dart"
|
||||||
|
relative: true
|
||||||
|
source: path
|
||||||
|
version: "1.0.0"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=3.6.0 <4.0.0"
|
dart: ">=3.6.1 <4.0.0"
|
||||||
flutter: ">=3.27.0"
|
flutter: ">=3.27.0"
|
||||||
|
@ -140,6 +140,10 @@ dependencies:
|
|||||||
git:
|
git:
|
||||||
url: https://github.com/Hexer10/youtube_explode_dart.git
|
url: https://github.com/Hexer10/youtube_explode_dart.git
|
||||||
ref: e519db65ad0b0a40b12f69285932f9db509da3cf
|
ref: e519db65ad0b0a40b12f69285932f9db509da3cf
|
||||||
|
yt_dlp_dart:
|
||||||
|
git:
|
||||||
|
url: https://github.com/KRTirtho/yt_dlp_dart.git
|
||||||
|
ref: 4199bb019542bae361fbb38b3448b3583fbca022
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
build_runner: ^2.4.13
|
build_runner: ^2.4.13
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
// ignore_for_file: type=lint
|
// ignore_for_file: type=lint
|
||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
import 'package:drift/internal/migrations.dart';
|
import 'package:drift/internal/migrations.dart';
|
||||||
|
import 'schema_v4.dart' as v4;
|
||||||
import 'schema_v3.dart' as v3;
|
import 'schema_v3.dart' as v3;
|
||||||
import 'schema_v2.dart' as v2;
|
import 'schema_v2.dart' as v2;
|
||||||
import 'schema_v1.dart' as v1;
|
import 'schema_v1.dart' as v1;
|
||||||
@ -11,6 +12,8 @@ class GeneratedHelper implements SchemaInstantiationHelper {
|
|||||||
@override
|
@override
|
||||||
GeneratedDatabase databaseForVersion(QueryExecutor db, int version) {
|
GeneratedDatabase databaseForVersion(QueryExecutor db, int version) {
|
||||||
switch (version) {
|
switch (version) {
|
||||||
|
case 4:
|
||||||
|
return v4.DatabaseAtV4(db);
|
||||||
case 3:
|
case 3:
|
||||||
return v3.DatabaseAtV3(db);
|
return v3.DatabaseAtV3(db);
|
||||||
case 2:
|
case 2:
|
||||||
@ -22,5 +25,5 @@ class GeneratedHelper implements SchemaInstantiationHelper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static const versions = const [1, 2, 3];
|
static const versions = const [1, 2, 3, 4];
|
||||||
}
|
}
|
||||||
|
3433
test/drift/app_db/generated/schema_v4.dart
Normal file
3433
test/drift/app_db/generated/schema_v4.dart
Normal file
File diff suppressed because it is too large
Load Diff
@ -15,7 +15,11 @@
|
|||||||
"no_tracks_listened_yet",
|
"no_tracks_listened_yet",
|
||||||
"not_following_artists",
|
"not_following_artists",
|
||||||
"no_favorite_albums_yet",
|
"no_favorite_albums_yet",
|
||||||
"no_logs_found"
|
"no_logs_found",
|
||||||
|
"youtube_engine",
|
||||||
|
"youtube_engine_not_installed_title",
|
||||||
|
"youtube_engine_not_installed_message",
|
||||||
|
"download"
|
||||||
],
|
],
|
||||||
|
|
||||||
"bn": [
|
"bn": [
|
||||||
@ -34,7 +38,11 @@
|
|||||||
"no_tracks_listened_yet",
|
"no_tracks_listened_yet",
|
||||||
"not_following_artists",
|
"not_following_artists",
|
||||||
"no_favorite_albums_yet",
|
"no_favorite_albums_yet",
|
||||||
"no_logs_found"
|
"no_logs_found",
|
||||||
|
"youtube_engine",
|
||||||
|
"youtube_engine_not_installed_title",
|
||||||
|
"youtube_engine_not_installed_message",
|
||||||
|
"download"
|
||||||
],
|
],
|
||||||
|
|
||||||
"ca": [
|
"ca": [
|
||||||
@ -53,7 +61,11 @@
|
|||||||
"no_tracks_listened_yet",
|
"no_tracks_listened_yet",
|
||||||
"not_following_artists",
|
"not_following_artists",
|
||||||
"no_favorite_albums_yet",
|
"no_favorite_albums_yet",
|
||||||
"no_logs_found"
|
"no_logs_found",
|
||||||
|
"youtube_engine",
|
||||||
|
"youtube_engine_not_installed_title",
|
||||||
|
"youtube_engine_not_installed_message",
|
||||||
|
"download"
|
||||||
],
|
],
|
||||||
|
|
||||||
"cs": [
|
"cs": [
|
||||||
@ -72,7 +84,11 @@
|
|||||||
"no_tracks_listened_yet",
|
"no_tracks_listened_yet",
|
||||||
"not_following_artists",
|
"not_following_artists",
|
||||||
"no_favorite_albums_yet",
|
"no_favorite_albums_yet",
|
||||||
"no_logs_found"
|
"no_logs_found",
|
||||||
|
"youtube_engine",
|
||||||
|
"youtube_engine_not_installed_title",
|
||||||
|
"youtube_engine_not_installed_message",
|
||||||
|
"download"
|
||||||
],
|
],
|
||||||
|
|
||||||
"de": [
|
"de": [
|
||||||
@ -91,7 +107,11 @@
|
|||||||
"no_tracks_listened_yet",
|
"no_tracks_listened_yet",
|
||||||
"not_following_artists",
|
"not_following_artists",
|
||||||
"no_favorite_albums_yet",
|
"no_favorite_albums_yet",
|
||||||
"no_logs_found"
|
"no_logs_found",
|
||||||
|
"youtube_engine",
|
||||||
|
"youtube_engine_not_installed_title",
|
||||||
|
"youtube_engine_not_installed_message",
|
||||||
|
"download"
|
||||||
],
|
],
|
||||||
|
|
||||||
"es": [
|
"es": [
|
||||||
@ -110,7 +130,11 @@
|
|||||||
"no_tracks_listened_yet",
|
"no_tracks_listened_yet",
|
||||||
"not_following_artists",
|
"not_following_artists",
|
||||||
"no_favorite_albums_yet",
|
"no_favorite_albums_yet",
|
||||||
"no_logs_found"
|
"no_logs_found",
|
||||||
|
"youtube_engine",
|
||||||
|
"youtube_engine_not_installed_title",
|
||||||
|
"youtube_engine_not_installed_message",
|
||||||
|
"download"
|
||||||
],
|
],
|
||||||
|
|
||||||
"eu": [
|
"eu": [
|
||||||
@ -129,7 +153,11 @@
|
|||||||
"no_tracks_listened_yet",
|
"no_tracks_listened_yet",
|
||||||
"not_following_artists",
|
"not_following_artists",
|
||||||
"no_favorite_albums_yet",
|
"no_favorite_albums_yet",
|
||||||
"no_logs_found"
|
"no_logs_found",
|
||||||
|
"youtube_engine",
|
||||||
|
"youtube_engine_not_installed_title",
|
||||||
|
"youtube_engine_not_installed_message",
|
||||||
|
"download"
|
||||||
],
|
],
|
||||||
|
|
||||||
"fa": [
|
"fa": [
|
||||||
@ -148,7 +176,11 @@
|
|||||||
"no_tracks_listened_yet",
|
"no_tracks_listened_yet",
|
||||||
"not_following_artists",
|
"not_following_artists",
|
||||||
"no_favorite_albums_yet",
|
"no_favorite_albums_yet",
|
||||||
"no_logs_found"
|
"no_logs_found",
|
||||||
|
"youtube_engine",
|
||||||
|
"youtube_engine_not_installed_title",
|
||||||
|
"youtube_engine_not_installed_message",
|
||||||
|
"download"
|
||||||
],
|
],
|
||||||
|
|
||||||
"fi": [
|
"fi": [
|
||||||
@ -167,7 +199,11 @@
|
|||||||
"no_tracks_listened_yet",
|
"no_tracks_listened_yet",
|
||||||
"not_following_artists",
|
"not_following_artists",
|
||||||
"no_favorite_albums_yet",
|
"no_favorite_albums_yet",
|
||||||
"no_logs_found"
|
"no_logs_found",
|
||||||
|
"youtube_engine",
|
||||||
|
"youtube_engine_not_installed_title",
|
||||||
|
"youtube_engine_not_installed_message",
|
||||||
|
"download"
|
||||||
],
|
],
|
||||||
|
|
||||||
"fr": [
|
"fr": [
|
||||||
@ -186,7 +222,11 @@
|
|||||||
"no_tracks_listened_yet",
|
"no_tracks_listened_yet",
|
||||||
"not_following_artists",
|
"not_following_artists",
|
||||||
"no_favorite_albums_yet",
|
"no_favorite_albums_yet",
|
||||||
"no_logs_found"
|
"no_logs_found",
|
||||||
|
"youtube_engine",
|
||||||
|
"youtube_engine_not_installed_title",
|
||||||
|
"youtube_engine_not_installed_message",
|
||||||
|
"download"
|
||||||
],
|
],
|
||||||
|
|
||||||
"hi": [
|
"hi": [
|
||||||
@ -205,7 +245,11 @@
|
|||||||
"no_tracks_listened_yet",
|
"no_tracks_listened_yet",
|
||||||
"not_following_artists",
|
"not_following_artists",
|
||||||
"no_favorite_albums_yet",
|
"no_favorite_albums_yet",
|
||||||
"no_logs_found"
|
"no_logs_found",
|
||||||
|
"youtube_engine",
|
||||||
|
"youtube_engine_not_installed_title",
|
||||||
|
"youtube_engine_not_installed_message",
|
||||||
|
"download"
|
||||||
],
|
],
|
||||||
|
|
||||||
"id": [
|
"id": [
|
||||||
@ -224,7 +268,11 @@
|
|||||||
"no_tracks_listened_yet",
|
"no_tracks_listened_yet",
|
||||||
"not_following_artists",
|
"not_following_artists",
|
||||||
"no_favorite_albums_yet",
|
"no_favorite_albums_yet",
|
||||||
"no_logs_found"
|
"no_logs_found",
|
||||||
|
"youtube_engine",
|
||||||
|
"youtube_engine_not_installed_title",
|
||||||
|
"youtube_engine_not_installed_message",
|
||||||
|
"download"
|
||||||
],
|
],
|
||||||
|
|
||||||
"it": [
|
"it": [
|
||||||
@ -243,7 +291,11 @@
|
|||||||
"no_tracks_listened_yet",
|
"no_tracks_listened_yet",
|
||||||
"not_following_artists",
|
"not_following_artists",
|
||||||
"no_favorite_albums_yet",
|
"no_favorite_albums_yet",
|
||||||
"no_logs_found"
|
"no_logs_found",
|
||||||
|
"youtube_engine",
|
||||||
|
"youtube_engine_not_installed_title",
|
||||||
|
"youtube_engine_not_installed_message",
|
||||||
|
"download"
|
||||||
],
|
],
|
||||||
|
|
||||||
"ja": [
|
"ja": [
|
||||||
@ -262,7 +314,11 @@
|
|||||||
"no_tracks_listened_yet",
|
"no_tracks_listened_yet",
|
||||||
"not_following_artists",
|
"not_following_artists",
|
||||||
"no_favorite_albums_yet",
|
"no_favorite_albums_yet",
|
||||||
"no_logs_found"
|
"no_logs_found",
|
||||||
|
"youtube_engine",
|
||||||
|
"youtube_engine_not_installed_title",
|
||||||
|
"youtube_engine_not_installed_message",
|
||||||
|
"download"
|
||||||
],
|
],
|
||||||
|
|
||||||
"ka": [
|
"ka": [
|
||||||
@ -281,7 +337,11 @@
|
|||||||
"no_tracks_listened_yet",
|
"no_tracks_listened_yet",
|
||||||
"not_following_artists",
|
"not_following_artists",
|
||||||
"no_favorite_albums_yet",
|
"no_favorite_albums_yet",
|
||||||
"no_logs_found"
|
"no_logs_found",
|
||||||
|
"youtube_engine",
|
||||||
|
"youtube_engine_not_installed_title",
|
||||||
|
"youtube_engine_not_installed_message",
|
||||||
|
"download"
|
||||||
],
|
],
|
||||||
|
|
||||||
"ko": [
|
"ko": [
|
||||||
@ -300,7 +360,11 @@
|
|||||||
"no_tracks_listened_yet",
|
"no_tracks_listened_yet",
|
||||||
"not_following_artists",
|
"not_following_artists",
|
||||||
"no_favorite_albums_yet",
|
"no_favorite_albums_yet",
|
||||||
"no_logs_found"
|
"no_logs_found",
|
||||||
|
"youtube_engine",
|
||||||
|
"youtube_engine_not_installed_title",
|
||||||
|
"youtube_engine_not_installed_message",
|
||||||
|
"download"
|
||||||
],
|
],
|
||||||
|
|
||||||
"ne": [
|
"ne": [
|
||||||
@ -319,7 +383,11 @@
|
|||||||
"no_tracks_listened_yet",
|
"no_tracks_listened_yet",
|
||||||
"not_following_artists",
|
"not_following_artists",
|
||||||
"no_favorite_albums_yet",
|
"no_favorite_albums_yet",
|
||||||
"no_logs_found"
|
"no_logs_found",
|
||||||
|
"youtube_engine",
|
||||||
|
"youtube_engine_not_installed_title",
|
||||||
|
"youtube_engine_not_installed_message",
|
||||||
|
"download"
|
||||||
],
|
],
|
||||||
|
|
||||||
"nl": [
|
"nl": [
|
||||||
@ -338,7 +406,11 @@
|
|||||||
"no_tracks_listened_yet",
|
"no_tracks_listened_yet",
|
||||||
"not_following_artists",
|
"not_following_artists",
|
||||||
"no_favorite_albums_yet",
|
"no_favorite_albums_yet",
|
||||||
"no_logs_found"
|
"no_logs_found",
|
||||||
|
"youtube_engine",
|
||||||
|
"youtube_engine_not_installed_title",
|
||||||
|
"youtube_engine_not_installed_message",
|
||||||
|
"download"
|
||||||
],
|
],
|
||||||
|
|
||||||
"pl": [
|
"pl": [
|
||||||
@ -357,7 +429,11 @@
|
|||||||
"no_tracks_listened_yet",
|
"no_tracks_listened_yet",
|
||||||
"not_following_artists",
|
"not_following_artists",
|
||||||
"no_favorite_albums_yet",
|
"no_favorite_albums_yet",
|
||||||
"no_logs_found"
|
"no_logs_found",
|
||||||
|
"youtube_engine",
|
||||||
|
"youtube_engine_not_installed_title",
|
||||||
|
"youtube_engine_not_installed_message",
|
||||||
|
"download"
|
||||||
],
|
],
|
||||||
|
|
||||||
"pt": [
|
"pt": [
|
||||||
@ -376,7 +452,11 @@
|
|||||||
"no_tracks_listened_yet",
|
"no_tracks_listened_yet",
|
||||||
"not_following_artists",
|
"not_following_artists",
|
||||||
"no_favorite_albums_yet",
|
"no_favorite_albums_yet",
|
||||||
"no_logs_found"
|
"no_logs_found",
|
||||||
|
"youtube_engine",
|
||||||
|
"youtube_engine_not_installed_title",
|
||||||
|
"youtube_engine_not_installed_message",
|
||||||
|
"download"
|
||||||
],
|
],
|
||||||
|
|
||||||
"ru": [
|
"ru": [
|
||||||
@ -395,7 +475,11 @@
|
|||||||
"no_tracks_listened_yet",
|
"no_tracks_listened_yet",
|
||||||
"not_following_artists",
|
"not_following_artists",
|
||||||
"no_favorite_albums_yet",
|
"no_favorite_albums_yet",
|
||||||
"no_logs_found"
|
"no_logs_found",
|
||||||
|
"youtube_engine",
|
||||||
|
"youtube_engine_not_installed_title",
|
||||||
|
"youtube_engine_not_installed_message",
|
||||||
|
"download"
|
||||||
],
|
],
|
||||||
|
|
||||||
"th": [
|
"th": [
|
||||||
@ -414,7 +498,11 @@
|
|||||||
"no_tracks_listened_yet",
|
"no_tracks_listened_yet",
|
||||||
"not_following_artists",
|
"not_following_artists",
|
||||||
"no_favorite_albums_yet",
|
"no_favorite_albums_yet",
|
||||||
"no_logs_found"
|
"no_logs_found",
|
||||||
|
"youtube_engine",
|
||||||
|
"youtube_engine_not_installed_title",
|
||||||
|
"youtube_engine_not_installed_message",
|
||||||
|
"download"
|
||||||
],
|
],
|
||||||
|
|
||||||
"tr": [
|
"tr": [
|
||||||
@ -433,7 +521,11 @@
|
|||||||
"no_tracks_listened_yet",
|
"no_tracks_listened_yet",
|
||||||
"not_following_artists",
|
"not_following_artists",
|
||||||
"no_favorite_albums_yet",
|
"no_favorite_albums_yet",
|
||||||
"no_logs_found"
|
"no_logs_found",
|
||||||
|
"youtube_engine",
|
||||||
|
"youtube_engine_not_installed_title",
|
||||||
|
"youtube_engine_not_installed_message",
|
||||||
|
"download"
|
||||||
],
|
],
|
||||||
|
|
||||||
"uk": [
|
"uk": [
|
||||||
@ -452,7 +544,11 @@
|
|||||||
"no_tracks_listened_yet",
|
"no_tracks_listened_yet",
|
||||||
"not_following_artists",
|
"not_following_artists",
|
||||||
"no_favorite_albums_yet",
|
"no_favorite_albums_yet",
|
||||||
"no_logs_found"
|
"no_logs_found",
|
||||||
|
"youtube_engine",
|
||||||
|
"youtube_engine_not_installed_title",
|
||||||
|
"youtube_engine_not_installed_message",
|
||||||
|
"download"
|
||||||
],
|
],
|
||||||
|
|
||||||
"vi": [
|
"vi": [
|
||||||
@ -471,7 +567,11 @@
|
|||||||
"no_tracks_listened_yet",
|
"no_tracks_listened_yet",
|
||||||
"not_following_artists",
|
"not_following_artists",
|
||||||
"no_favorite_albums_yet",
|
"no_favorite_albums_yet",
|
||||||
"no_logs_found"
|
"no_logs_found",
|
||||||
|
"youtube_engine",
|
||||||
|
"youtube_engine_not_installed_title",
|
||||||
|
"youtube_engine_not_installed_message",
|
||||||
|
"download"
|
||||||
],
|
],
|
||||||
|
|
||||||
"zh": [
|
"zh": [
|
||||||
@ -490,6 +590,10 @@
|
|||||||
"no_tracks_listened_yet",
|
"no_tracks_listened_yet",
|
||||||
"not_following_artists",
|
"not_following_artists",
|
||||||
"no_favorite_albums_yet",
|
"no_favorite_albums_yet",
|
||||||
"no_logs_found"
|
"no_logs_found",
|
||||||
|
"youtube_engine",
|
||||||
|
"youtube_engine_not_installed_title",
|
||||||
|
"youtube_engine_not_installed_message",
|
||||||
|
"download"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user