mirror of
https://github.com/KRTirtho/spotube.git
synced 2025-09-13 07:55:18 +00:00
feat: use catcher to handle exceptions
This commit is contained in:
parent
013aac4543
commit
84d94b05bc
@ -20,33 +20,28 @@ class PlayPauseAction extends Action<PlayPauseIntent> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
invoke(intent) async {
|
invoke(intent) async {
|
||||||
try {
|
if (PlayerControls.focusNode.canRequestFocus) {
|
||||||
if (PlayerControls.focusNode.canRequestFocus) {
|
PlayerControls.focusNode.requestFocus();
|
||||||
PlayerControls.focusNode.requestFocus();
|
|
||||||
}
|
|
||||||
final playback = intent.ref.read(playbackProvider);
|
|
||||||
if (playback.track == null) {
|
|
||||||
return null;
|
|
||||||
} else if (playback.track != null &&
|
|
||||||
playback.currentDuration == Duration.zero &&
|
|
||||||
await playback.player.getCurrentPosition() == Duration.zero) {
|
|
||||||
if (playback.track!.ytUri.startsWith("http")) {
|
|
||||||
final track = Track.fromJson(playback.track!.toJson());
|
|
||||||
playback.track = null;
|
|
||||||
await playback.play(track);
|
|
||||||
} else {
|
|
||||||
final track = playback.track;
|
|
||||||
playback.track = null;
|
|
||||||
await playback.play(track!);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
await playback.togglePlayPause();
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
} catch (e, stack) {
|
|
||||||
logger.e("useTogglePlayPause", e, stack);
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
final playback = intent.ref.read(playbackProvider);
|
||||||
|
if (playback.track == null) {
|
||||||
|
return null;
|
||||||
|
} else if (playback.track != null &&
|
||||||
|
playback.currentDuration == Duration.zero &&
|
||||||
|
await playback.player.getCurrentPosition() == Duration.zero) {
|
||||||
|
if (playback.track!.ytUri.startsWith("http")) {
|
||||||
|
final track = Track.fromJson(playback.track!.toJson());
|
||||||
|
playback.track = null;
|
||||||
|
await playback.play(track);
|
||||||
|
} else {
|
||||||
|
final track = playback.track;
|
||||||
|
playback.track = null;
|
||||||
|
await playback.play(track!);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
await playback.togglePlayPause();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import 'package:catcher/catcher.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:spotify/spotify.dart' hide Search;
|
import 'package:spotify/spotify.dart' hide Search;
|
||||||
@ -19,7 +20,7 @@ import 'package:spotube/pages/search/search.dart';
|
|||||||
import 'package:spotube/pages/settings/settings.dart';
|
import 'package:spotube/pages/settings/settings.dart';
|
||||||
import 'package:spotube/pages/mobile_login/mobile_login.dart';
|
import 'package:spotube/pages/mobile_login/mobile_login.dart';
|
||||||
|
|
||||||
final rootNavigatorKey = GlobalKey<NavigatorState>();
|
final rootNavigatorKey = Catcher.navigatorKey;
|
||||||
final shellRouteNavigatorKey = GlobalKey<NavigatorState>();
|
final shellRouteNavigatorKey = GlobalKey<NavigatorState>();
|
||||||
final router = GoRouter(
|
final router = GoRouter(
|
||||||
navigatorKey: rootNavigatorKey,
|
navigatorKey: rootNavigatorKey,
|
||||||
|
@ -60,5 +60,5 @@ abstract class SpotubeIcons {
|
|||||||
static const info = FeatherIcons.info;
|
static const info = FeatherIcons.info;
|
||||||
static const userRemove = FeatherIcons.userX;
|
static const userRemove = FeatherIcons.userX;
|
||||||
static const close = FeatherIcons.x;
|
static const close = FeatherIcons.x;
|
||||||
static const minimize = FeatherIcons.minimize2;
|
static const minimize = FeatherIcons.chevronDown;
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:catcher/catcher.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
@ -98,12 +99,11 @@ final localTracksProvider = FutureProvider<List<Track>>((ref) async {
|
|||||||
} on FfiException catch (e) {
|
} on FfiException catch (e) {
|
||||||
if (e.message == "NoTag: reader does not contain an id3 tag") {
|
if (e.message == "NoTag: reader does not contain an id3 tag") {
|
||||||
getLogger(FutureProvider<List<Track>>)
|
getLogger(FutureProvider<List<Track>>)
|
||||||
.w("[Fetching metadata]", e.message);
|
.v("[Fetching metadata]", e.message);
|
||||||
}
|
}
|
||||||
return {};
|
return {};
|
||||||
} on Exception catch (e, stack) {
|
} catch (e, stack) {
|
||||||
getLogger(FutureProvider<List<Track>>)
|
Catcher.reportCheckedError(e, stack);
|
||||||
.e("[Fetching metadata]", e, stack);
|
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -124,7 +124,7 @@ final localTracksProvider = FutureProvider<List<Track>>((ref) async {
|
|||||||
|
|
||||||
return tracks;
|
return tracks;
|
||||||
} catch (e, stack) {
|
} catch (e, stack) {
|
||||||
getLogger(FutureProvider).e("[LocalTracksProvider]", e, stack);
|
Catcher.reportCheckedError(e, stack);
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -199,15 +199,7 @@ class PlayerControls extends HookConsumerWidget {
|
|||||||
SpotubeIcons.stop,
|
SpotubeIcons.stop,
|
||||||
color: iconColor,
|
color: iconColor,
|
||||||
),
|
),
|
||||||
onPressed: playback.track != null
|
onPressed: playback.track != null ? playback.stop : null,
|
||||||
? () async {
|
|
||||||
try {
|
|
||||||
await playback.stop();
|
|
||||||
} catch (e, stack) {
|
|
||||||
logger.e("onStop", e, stack);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
: null,
|
|
||||||
),
|
),
|
||||||
PlatformIconButton(
|
PlatformIconButton(
|
||||||
tooltip:
|
tooltip:
|
||||||
|
@ -145,14 +145,10 @@ class BottomPlayer extends HookConsumerWidget {
|
|||||||
volume.value = v;
|
volume.value = v;
|
||||||
},
|
},
|
||||||
onChangeEnd: (value) async {
|
onChangeEnd: (value) async {
|
||||||
try {
|
// You don't really need to know why but this
|
||||||
// You don't really need to know why but this
|
// way it works only
|
||||||
// way it works only
|
await playback.setVolume(value);
|
||||||
await playback.setVolume(value);
|
await playback.setVolume(value);
|
||||||
await playback.setVolume(value);
|
|
||||||
} catch (e, stack) {
|
|
||||||
logger.e("onChange", e, stack);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -10,7 +10,6 @@ import 'package:audio_service_web/audio_service_web.dart';
|
|||||||
import 'package:audio_session/audio_session_web.dart';
|
import 'package:audio_session/audio_session_web.dart';
|
||||||
import 'package:audioplayers_web/audioplayers_web.dart';
|
import 'package:audioplayers_web/audioplayers_web.dart';
|
||||||
import 'package:file_picker/_internal/file_picker_web.dart';
|
import 'package:file_picker/_internal/file_picker_web.dart';
|
||||||
import 'package:package_info_plus_web/package_info_plus_web.dart';
|
|
||||||
import 'package:shared_preferences_web/shared_preferences_web.dart';
|
import 'package:shared_preferences_web/shared_preferences_web.dart';
|
||||||
import 'package:url_launcher_web/url_launcher_web.dart';
|
import 'package:url_launcher_web/url_launcher_web.dart';
|
||||||
|
|
||||||
@ -22,7 +21,6 @@ void registerPlugins(Registrar registrar) {
|
|||||||
AudioSessionWeb.registerWith(registrar);
|
AudioSessionWeb.registerWith(registrar);
|
||||||
AudioplayersPlugin.registerWith(registrar);
|
AudioplayersPlugin.registerWith(registrar);
|
||||||
FilePickerWeb.registerWith(registrar);
|
FilePickerWeb.registerWith(registrar);
|
||||||
PackageInfoPlugin.registerWith(registrar);
|
|
||||||
SharedPreferencesPlugin.registerWith(registrar);
|
SharedPreferencesPlugin.registerWith(registrar);
|
||||||
UrlLauncherPlugin.registerWith(registrar);
|
UrlLauncherPlugin.registerWith(registrar);
|
||||||
registrar.registerMessageHandler();
|
registrar.registerMessageHandler();
|
||||||
|
@ -1,31 +1,20 @@
|
|||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:spotube/models/logger.dart';
|
|
||||||
import 'package:spotube/provider/playback_provider.dart';
|
import 'package:spotube/provider/playback_provider.dart';
|
||||||
|
|
||||||
final logger = getLogger("PlaybackHook");
|
|
||||||
|
|
||||||
Future<void> Function() useNextTrack(WidgetRef ref) {
|
Future<void> Function() useNextTrack(WidgetRef ref) {
|
||||||
return () async {
|
return () async {
|
||||||
try {
|
final playback = ref.read(playbackProvider);
|
||||||
final playback = ref.read(playbackProvider);
|
await playback.player.pause();
|
||||||
await playback.player.pause();
|
await playback.player.seek(Duration.zero);
|
||||||
await playback.player.seek(Duration.zero);
|
playback.seekForward();
|
||||||
playback.seekForward();
|
|
||||||
} catch (e, stack) {
|
|
||||||
logger.e("useNextTrack", e, stack);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> Function() usePreviousTrack(WidgetRef ref) {
|
Future<void> Function() usePreviousTrack(WidgetRef ref) {
|
||||||
return () async {
|
return () async {
|
||||||
try {
|
final playback = ref.read(playbackProvider);
|
||||||
final playback = ref.read(playbackProvider);
|
await playback.player.pause();
|
||||||
await playback.player.pause();
|
await playback.player.seek(Duration.zero);
|
||||||
await playback.player.seek(Duration.zero);
|
playback.seekBackward();
|
||||||
playback.seekBackward();
|
|
||||||
} catch (e, stack) {
|
|
||||||
logger.e("onPrevious", e, stack);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
165
lib/main.dart
165
lib/main.dart
@ -1,6 +1,7 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:audio_service/audio_service.dart';
|
import 'package:audio_service/audio_service.dart';
|
||||||
|
import 'package:catcher/catcher.dart';
|
||||||
import 'package:fl_query/fl_query.dart';
|
import 'package:fl_query/fl_query.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
@ -61,82 +62,98 @@ void main() async {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
MobileAudioService? audioServiceHandler;
|
MobileAudioService? audioServiceHandler;
|
||||||
runApp(
|
|
||||||
Builder(
|
|
||||||
builder: (context) {
|
|
||||||
return ProviderScope(
|
|
||||||
overrides: [
|
|
||||||
playbackProvider.overrideWith(
|
|
||||||
(ref) {
|
|
||||||
final youtube = ref.watch(youtubeProvider);
|
|
||||||
final player = ref.watch(audioPlayerProvider);
|
|
||||||
|
|
||||||
final playback = Playback(
|
Catcher(
|
||||||
player: player,
|
debugConfig: CatcherOptions(
|
||||||
youtube: youtube,
|
SilentReportMode(),
|
||||||
ref: ref,
|
[
|
||||||
);
|
ConsoleHandler(
|
||||||
|
enableDeviceParameters: false,
|
||||||
if (audioServiceHandler == null) {
|
enableApplicationParameters: false,
|
||||||
AudioService.init(
|
),
|
||||||
builder: () => MobileAudioService(playback),
|
FileHandler(await getLogsPath(), printLogs: false),
|
||||||
config: const AudioServiceConfig(
|
SnackbarHandler(const Duration(seconds: 5)),
|
||||||
androidNotificationChannelId: 'com.krtirtho.Spotube',
|
],
|
||||||
androidNotificationChannelName: 'Spotube',
|
|
||||||
androidNotificationOngoing: true,
|
|
||||||
),
|
|
||||||
).then(
|
|
||||||
(value) {
|
|
||||||
playback.mobileAudioService = value;
|
|
||||||
audioServiceHandler = value;
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return playback;
|
|
||||||
},
|
|
||||||
),
|
|
||||||
downloaderProvider.overrideWith(
|
|
||||||
(ref) {
|
|
||||||
return Downloader(
|
|
||||||
ref,
|
|
||||||
queueInstance,
|
|
||||||
yt: ref.watch(youtubeProvider),
|
|
||||||
downloadPath: ref.watch(
|
|
||||||
userPreferencesProvider.select(
|
|
||||||
(s) => s.downloadLocation,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
onFileExists: (track) {
|
|
||||||
final logger = getLogger(Downloader);
|
|
||||||
try {
|
|
||||||
logger.v(
|
|
||||||
"[onFileExists] download confirmation for ${track.name}",
|
|
||||||
);
|
|
||||||
return showPlatformAlertDialog<bool>(
|
|
||||||
context,
|
|
||||||
builder: (_) => ReplaceDownloadedDialog(track: track),
|
|
||||||
).then((s) => s ?? false);
|
|
||||||
} catch (e, stack) {
|
|
||||||
logger.e(
|
|
||||||
"onFileExists",
|
|
||||||
e,
|
|
||||||
stack,
|
|
||||||
);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
)
|
|
||||||
],
|
|
||||||
child: QueryBowlScope(
|
|
||||||
bowl: bowl,
|
|
||||||
child: const Spotube(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
|
releaseConfig: CatcherOptions(SilentReportMode(), [
|
||||||
|
FileHandler(await getLogsPath(), printLogs: false),
|
||||||
|
]),
|
||||||
|
runAppFunction: () {
|
||||||
|
runApp(
|
||||||
|
Builder(
|
||||||
|
builder: (context) {
|
||||||
|
return ProviderScope(
|
||||||
|
overrides: [
|
||||||
|
playbackProvider.overrideWith(
|
||||||
|
(ref) {
|
||||||
|
final youtube = ref.watch(youtubeProvider);
|
||||||
|
final player = ref.watch(audioPlayerProvider);
|
||||||
|
|
||||||
|
final playback = Playback(
|
||||||
|
player: player,
|
||||||
|
youtube: youtube,
|
||||||
|
ref: ref,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (audioServiceHandler == null) {
|
||||||
|
AudioService.init(
|
||||||
|
builder: () => MobileAudioService(playback),
|
||||||
|
config: const AudioServiceConfig(
|
||||||
|
androidNotificationChannelId: 'com.krtirtho.Spotube',
|
||||||
|
androidNotificationChannelName: 'Spotube',
|
||||||
|
androidNotificationOngoing: true,
|
||||||
|
),
|
||||||
|
).then(
|
||||||
|
(value) {
|
||||||
|
playback.mobileAudioService = value;
|
||||||
|
audioServiceHandler = value;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return playback;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
downloaderProvider.overrideWith(
|
||||||
|
(ref) {
|
||||||
|
return Downloader(
|
||||||
|
ref,
|
||||||
|
queueInstance,
|
||||||
|
yt: ref.watch(youtubeProvider),
|
||||||
|
downloadPath: ref.watch(
|
||||||
|
userPreferencesProvider.select(
|
||||||
|
(s) => s.downloadLocation,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onFileExists: (track) {
|
||||||
|
final logger = getLogger(Downloader);
|
||||||
|
try {
|
||||||
|
logger.v(
|
||||||
|
"[onFileExists] download confirmation for ${track.name}",
|
||||||
|
);
|
||||||
|
return showPlatformAlertDialog<bool>(
|
||||||
|
context,
|
||||||
|
builder: (_) =>
|
||||||
|
ReplaceDownloadedDialog(track: track),
|
||||||
|
).then((s) => s ?? false);
|
||||||
|
} catch (e, stack) {
|
||||||
|
Catcher.reportCheckedError(e, stack);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
)
|
||||||
|
],
|
||||||
|
child: QueryBowlScope(
|
||||||
|
bowl: bowl,
|
||||||
|
child: const Spotube(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,6 +13,22 @@ SpotubeLogger getLogger<T>(T owner) {
|
|||||||
return _loggerFactory;
|
return _loggerFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<File> getLogsPath() async {
|
||||||
|
String dir = (await getApplicationDocumentsDirectory()).path;
|
||||||
|
if (kIsAndroid) {
|
||||||
|
dir = (await getExternalStorageDirectory())?.path ?? "";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (kIsMacOS) {
|
||||||
|
dir = path.join((await getLibraryDirectory()).path, "Logs");
|
||||||
|
}
|
||||||
|
final file = File(path.join(dir, ".spotube_logs"));
|
||||||
|
if (!await file.exists()) {
|
||||||
|
await file.create();
|
||||||
|
}
|
||||||
|
return file;
|
||||||
|
}
|
||||||
|
|
||||||
class SpotubeLogger extends Logger {
|
class SpotubeLogger extends Logger {
|
||||||
String? owner;
|
String? owner;
|
||||||
SpotubeLogger([this.owner]) : super(filter: _SpotubeLogFilter());
|
SpotubeLogger([this.owner]) : super(filter: _SpotubeLogFilter());
|
||||||
|
@ -203,12 +203,6 @@ class ArtistPage extends HookConsumerWidget {
|
|||||||
.queryKey,
|
.queryKey,
|
||||||
)
|
)
|
||||||
?.refetch();
|
?.refetch();
|
||||||
} catch (e, stack) {
|
|
||||||
logger.e(
|
|
||||||
"FollowButton.onPressed",
|
|
||||||
e,
|
|
||||||
stack,
|
|
||||||
);
|
|
||||||
} finally {
|
} finally {
|
||||||
QueryBowl.of(context)
|
QueryBowl.of(context)
|
||||||
.refetchQueries([
|
.refetchQueries([
|
||||||
|
@ -134,12 +134,8 @@ class Downloader with ChangeNotifier {
|
|||||||
logger.v(
|
logger.v(
|
||||||
"[addToQueue] Writing metadata to ${file.path} is successful",
|
"[addToQueue] Writing metadata to ${file.path} is successful",
|
||||||
);
|
);
|
||||||
} catch (e, stack) {
|
} catch (e) {
|
||||||
logger.e(
|
logger.v("[addToQueue] Failed download of ${file.path}", e);
|
||||||
"[addToQueue] Failed download of ${file.path}",
|
|
||||||
e,
|
|
||||||
stack,
|
|
||||||
);
|
|
||||||
rethrow;
|
rethrow;
|
||||||
} finally {
|
} finally {
|
||||||
currentlyRunning--;
|
currentlyRunning--;
|
||||||
|
@ -3,6 +3,7 @@ import 'dart:convert';
|
|||||||
|
|
||||||
import 'package:audio_service/audio_service.dart';
|
import 'package:audio_service/audio_service.dart';
|
||||||
import 'package:audioplayers/audioplayers.dart';
|
import 'package:audioplayers/audioplayers.dart';
|
||||||
|
import 'package:catcher/catcher.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
@ -198,75 +199,67 @@ class Playback extends PersistedChangeNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> playPlaylist(CurrentPlaylist playlist, [int index = 0]) async {
|
Future<void> playPlaylist(CurrentPlaylist playlist, [int index = 0]) async {
|
||||||
try {
|
if (index < 0 || index > playlist.tracks.length - 1) return;
|
||||||
if (index < 0 || index > playlist.tracks.length - 1) return;
|
if (isPlaying || status == PlaybackStatus.playing) await stop();
|
||||||
if (isPlaying || status == PlaybackStatus.playing) await stop();
|
this.playlist = blacklistNotifier.filterPlaylist(playlist);
|
||||||
this.playlist = blacklistNotifier.filterPlaylist(playlist);
|
mobileAudioService?.session?.setActive(true);
|
||||||
mobileAudioService?.session?.setActive(true);
|
final played = this.playlist!.tracks[index];
|
||||||
final played = this.playlist!.tracks[index];
|
status = PlaybackStatus.loading;
|
||||||
status = PlaybackStatus.loading;
|
notifyListeners();
|
||||||
notifyListeners();
|
await play(played).then((_) {
|
||||||
await play(played).then((_) {
|
int i = this
|
||||||
int i = this
|
.playlist!
|
||||||
.playlist!
|
.tracks
|
||||||
.tracks
|
.indexWhere((element) => element.id == played.id);
|
||||||
.indexWhere((element) => element.id == played.id);
|
if (index == -1) return;
|
||||||
if (index == -1) return;
|
this.playlist!.tracks[i] = track!;
|
||||||
this.playlist!.tracks[i] = track!;
|
});
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
_logger.e("[playPlaylist] $e");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// player methods
|
// player methods
|
||||||
Future<void> play(Track track, {AudioOnlyStreamInfo? manifest}) async {
|
Future<void> play(Track track, {AudioOnlyStreamInfo? manifest}) async {
|
||||||
_logger.v("[Track Playing] ${track.name} - ${track.id}");
|
_logger.v("[Track Playing] ${track.name} - ${track.id}");
|
||||||
try {
|
// the track is already playing so no need to change that
|
||||||
// the track is already playing so no need to change that
|
if (track.id == this.track?.id) return;
|
||||||
if (track.id == this.track?.id) return;
|
if (status != PlaybackStatus.loading) {
|
||||||
if (status != PlaybackStatus.loading) {
|
status = PlaybackStatus.loading;
|
||||||
status = PlaybackStatus.loading;
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
_siblingYtVideos = [];
|
|
||||||
|
|
||||||
// the track is not a SpotubeTrack so turning it to one
|
|
||||||
if (track is! SpotubeTrack) {
|
|
||||||
final s = await toSpotubeTrack(track);
|
|
||||||
track = s.item1;
|
|
||||||
manifest = s.item2;
|
|
||||||
}
|
|
||||||
|
|
||||||
final tag = MediaItem(
|
|
||||||
id: track.id!,
|
|
||||||
title: track.name!,
|
|
||||||
album: track.album?.name,
|
|
||||||
artist: TypeConversionUtils.artists_X_String(
|
|
||||||
track.artists ?? <ArtistSimple>[]),
|
|
||||||
artUri: Uri.parse(
|
|
||||||
TypeConversionUtils.image_X_UrlString(
|
|
||||||
track.album?.images,
|
|
||||||
placeholder: ImagePlaceholder.online,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
duration: track.ytTrack.duration,
|
|
||||||
);
|
|
||||||
mobileAudioService?.addItem(tag);
|
|
||||||
_logger.v("[Track Direct Source] - ${(track).ytUri}");
|
|
||||||
this.track = track;
|
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
updatePersistence();
|
|
||||||
await player.play(
|
|
||||||
track.ytUri.startsWith("http")
|
|
||||||
? await getAppropriateSource(track, manifest)
|
|
||||||
: DeviceFileSource(track.ytUri),
|
|
||||||
);
|
|
||||||
status = PlaybackStatus.playing;
|
|
||||||
notifyListeners();
|
|
||||||
} catch (e, stack) {
|
|
||||||
_logger.e("play", e, stack);
|
|
||||||
}
|
}
|
||||||
|
_siblingYtVideos = [];
|
||||||
|
|
||||||
|
// the track is not a SpotubeTrack so turning it to one
|
||||||
|
if (track is! SpotubeTrack) {
|
||||||
|
final s = await toSpotubeTrack(track);
|
||||||
|
track = s.item1;
|
||||||
|
manifest = s.item2;
|
||||||
|
}
|
||||||
|
|
||||||
|
final tag = MediaItem(
|
||||||
|
id: track.id!,
|
||||||
|
title: track.name!,
|
||||||
|
album: track.album?.name,
|
||||||
|
artist: TypeConversionUtils.artists_X_String(
|
||||||
|
track.artists ?? <ArtistSimple>[]),
|
||||||
|
artUri: Uri.parse(
|
||||||
|
TypeConversionUtils.image_X_UrlString(
|
||||||
|
track.album?.images,
|
||||||
|
placeholder: ImagePlaceholder.online,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
duration: track.ytTrack.duration,
|
||||||
|
);
|
||||||
|
mobileAudioService?.addItem(tag);
|
||||||
|
_logger.v("[Track Direct Source] - ${(track).ytUri}");
|
||||||
|
this.track = track;
|
||||||
|
notifyListeners();
|
||||||
|
updatePersistence();
|
||||||
|
await player.play(
|
||||||
|
track.ytUri.startsWith("http")
|
||||||
|
? await getAppropriateSource(track, manifest)
|
||||||
|
: DeviceFileSource(track.ytUri),
|
||||||
|
);
|
||||||
|
status = PlaybackStatus.playing;
|
||||||
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> resume() async {
|
Future<void> resume() async {
|
||||||
@ -382,7 +375,7 @@ class Playback extends PersistedChangeNotifier {
|
|||||||
);
|
);
|
||||||
return List.castFrom<dynamic, Map<String, int>>(segments);
|
return List.castFrom<dynamic, Map<String, int>>(segments);
|
||||||
} catch (e, stack) {
|
} catch (e, stack) {
|
||||||
_logger.e("[getSkipSegments]", e, stack);
|
Catcher.reportCheckedError(e, stack);
|
||||||
return List.castFrom<dynamic, Map<String, int>>([]);
|
return List.castFrom<dynamic, Map<String, int>>([]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -465,112 +458,104 @@ class Playback extends PersistedChangeNotifier {
|
|||||||
bool noSponsorBlock = false,
|
bool noSponsorBlock = false,
|
||||||
bool ignoreCache = false,
|
bool ignoreCache = false,
|
||||||
}) async {
|
}) async {
|
||||||
try {
|
final format = preferences.ytSearchFormat;
|
||||||
final format = preferences.ytSearchFormat;
|
final matchAlgorithm = preferences.trackMatchAlgorithm;
|
||||||
final matchAlgorithm = preferences.trackMatchAlgorithm;
|
final artistsName =
|
||||||
final artistsName = track.artists
|
track.artists?.map((ar) => ar.name).toList().whereNotNull().toList() ??
|
||||||
?.map((ar) => ar.name)
|
[];
|
||||||
.toList()
|
_logger.v("[Track Search Artists] $artistsName");
|
||||||
.whereNotNull()
|
final mainArtist = artistsName.first;
|
||||||
.toList() ??
|
final featuredArtists = artistsName.length > 1
|
||||||
[];
|
? "feat. ${artistsName.sublist(1).join(" ")}"
|
||||||
_logger.v("[Track Search Artists] $artistsName");
|
: "";
|
||||||
final mainArtist = artistsName.first;
|
final title = ServiceUtils.getTitle(
|
||||||
final featuredArtists = artistsName.length > 1
|
track.name!,
|
||||||
? "feat. ${artistsName.sublist(1).join(" ")}"
|
artists: artistsName,
|
||||||
: "";
|
onlyCleanArtist: true,
|
||||||
final title = ServiceUtils.getTitle(
|
).trim();
|
||||||
track.name!,
|
_logger.v("[Track Search Title] $title");
|
||||||
artists: artistsName,
|
final queryString = format
|
||||||
onlyCleanArtist: true,
|
.replaceAll("\$MAIN_ARTIST", mainArtist)
|
||||||
).trim();
|
.replaceAll("\$TITLE", title)
|
||||||
_logger.v("[Track Search Title] $title");
|
.replaceAll("\$FEATURED_ARTISTS", featuredArtists);
|
||||||
final queryString = format
|
_logger.v("[Youtube Search Term] $queryString");
|
||||||
.replaceAll("\$MAIN_ARTIST", mainArtist)
|
|
||||||
.replaceAll("\$TITLE", title)
|
|
||||||
.replaceAll("\$FEATURED_ARTISTS", featuredArtists);
|
|
||||||
_logger.v("[Youtube Search Term] $queryString");
|
|
||||||
|
|
||||||
Video ytVideo;
|
Video ytVideo;
|
||||||
final cachedTrack = await cache.get(track.id);
|
final cachedTrack = await cache.get(track.id);
|
||||||
if (cachedTrack != null &&
|
if (cachedTrack != null &&
|
||||||
cachedTrack.mode == matchAlgorithm.name &&
|
cachedTrack.mode == matchAlgorithm.name &&
|
||||||
!ignoreCache) {
|
!ignoreCache) {
|
||||||
_logger.v(
|
_logger.v(
|
||||||
"[Playing track from cache] youtubeId: ${cachedTrack.id} mode: ${cachedTrack.mode}",
|
"[Playing track from cache] youtubeId: ${cachedTrack.id} mode: ${cachedTrack.mode}",
|
||||||
);
|
|
||||||
ytVideo = VideoFromCacheTrackExtension.fromCacheTrack(cachedTrack);
|
|
||||||
} else {
|
|
||||||
VideoSearchList videos =
|
|
||||||
await raceMultiple(() => youtube.search.search(queryString));
|
|
||||||
if (matchAlgorithm != SpotubeTrackMatchAlgorithm.youtube) {
|
|
||||||
List<Map> ratedRankedVideos = videos
|
|
||||||
.map((video) {
|
|
||||||
// the find should be lazy thus everything case insensitive
|
|
||||||
final ytTitle = video.title.toLowerCase();
|
|
||||||
final bool hasTitle = ytTitle.contains(title);
|
|
||||||
final bool hasAllArtists = track.artists?.every(
|
|
||||||
(artist) => ytTitle.contains(artist.name!.toLowerCase()),
|
|
||||||
) ??
|
|
||||||
false;
|
|
||||||
final bool authorIsArtist =
|
|
||||||
track.artists?.first.name?.toLowerCase() ==
|
|
||||||
video.author.toLowerCase();
|
|
||||||
|
|
||||||
final bool hasNoLiveInTitle =
|
|
||||||
!PrimitiveUtils.containsTextInBracket(ytTitle, "live");
|
|
||||||
final bool hasCloseDuration =
|
|
||||||
(track.duration!.inSeconds - video.duration!.inSeconds)
|
|
||||||
.abs() <=
|
|
||||||
10; //Duration matching threshold
|
|
||||||
|
|
||||||
int rate = 0;
|
|
||||||
for (final el in [
|
|
||||||
hasTitle,
|
|
||||||
hasAllArtists,
|
|
||||||
if (matchAlgorithm ==
|
|
||||||
SpotubeTrackMatchAlgorithm.authenticPopular)
|
|
||||||
authorIsArtist,
|
|
||||||
hasNoLiveInTitle,
|
|
||||||
hasCloseDuration,
|
|
||||||
!video.isLive,
|
|
||||||
]) {
|
|
||||||
if (el) rate++;
|
|
||||||
}
|
|
||||||
// can't let pass any non title matching track
|
|
||||||
if (!hasTitle) rate = rate - 2;
|
|
||||||
|
|
||||||
return {
|
|
||||||
"video": video,
|
|
||||||
"points": rate,
|
|
||||||
"views": video.engagement.viewCount,
|
|
||||||
};
|
|
||||||
})
|
|
||||||
.toList()
|
|
||||||
.sortByProperties(
|
|
||||||
[false, false],
|
|
||||||
["points", "views"],
|
|
||||||
);
|
|
||||||
|
|
||||||
ytVideo = ratedRankedVideos.first["video"] as Video;
|
|
||||||
_siblingYtVideos =
|
|
||||||
ratedRankedVideos.map((e) => e["video"] as Video).toList();
|
|
||||||
notifyListeners();
|
|
||||||
} else {
|
|
||||||
ytVideo = videos.where((video) => !video.isLive).first;
|
|
||||||
_siblingYtVideos = videos.take(10).toList();
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ytVideoToSpotubeTrack(
|
|
||||||
ytVideo,
|
|
||||||
track,
|
|
||||||
noSponsorBlock: noSponsorBlock,
|
|
||||||
);
|
);
|
||||||
} catch (e, stack) {
|
ytVideo = VideoFromCacheTrackExtension.fromCacheTrack(cachedTrack);
|
||||||
_logger.e("topSpotubeTrack", e, stack);
|
} else {
|
||||||
rethrow;
|
VideoSearchList videos =
|
||||||
|
await raceMultiple(() => youtube.search.search(queryString));
|
||||||
|
if (matchAlgorithm != SpotubeTrackMatchAlgorithm.youtube) {
|
||||||
|
List<Map> ratedRankedVideos = videos
|
||||||
|
.map((video) {
|
||||||
|
// the find should be lazy thus everything case insensitive
|
||||||
|
final ytTitle = video.title.toLowerCase();
|
||||||
|
final bool hasTitle = ytTitle.contains(title);
|
||||||
|
final bool hasAllArtists = track.artists?.every(
|
||||||
|
(artist) => ytTitle.contains(artist.name!.toLowerCase()),
|
||||||
|
) ??
|
||||||
|
false;
|
||||||
|
final bool authorIsArtist =
|
||||||
|
track.artists?.first.name?.toLowerCase() ==
|
||||||
|
video.author.toLowerCase();
|
||||||
|
|
||||||
|
final bool hasNoLiveInTitle =
|
||||||
|
!PrimitiveUtils.containsTextInBracket(ytTitle, "live");
|
||||||
|
final bool hasCloseDuration =
|
||||||
|
(track.duration!.inSeconds - video.duration!.inSeconds)
|
||||||
|
.abs() <=
|
||||||
|
10; //Duration matching threshold
|
||||||
|
|
||||||
|
int rate = 0;
|
||||||
|
for (final el in [
|
||||||
|
hasTitle,
|
||||||
|
hasAllArtists,
|
||||||
|
if (matchAlgorithm ==
|
||||||
|
SpotubeTrackMatchAlgorithm.authenticPopular)
|
||||||
|
authorIsArtist,
|
||||||
|
hasNoLiveInTitle,
|
||||||
|
hasCloseDuration,
|
||||||
|
!video.isLive,
|
||||||
|
]) {
|
||||||
|
if (el) rate++;
|
||||||
|
}
|
||||||
|
// can't let pass any non title matching track
|
||||||
|
if (!hasTitle) rate = rate - 2;
|
||||||
|
|
||||||
|
return {
|
||||||
|
"video": video,
|
||||||
|
"points": rate,
|
||||||
|
"views": video.engagement.viewCount,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.toList()
|
||||||
|
.sortByProperties(
|
||||||
|
[false, false],
|
||||||
|
["points", "views"],
|
||||||
|
);
|
||||||
|
|
||||||
|
ytVideo = ratedRankedVideos.first["video"] as Video;
|
||||||
|
_siblingYtVideos =
|
||||||
|
ratedRankedVideos.map((e) => e["video"] as Video).toList();
|
||||||
|
notifyListeners();
|
||||||
|
} else {
|
||||||
|
ytVideo = videos.where((video) => !video.isLive).first;
|
||||||
|
_siblingYtVideos = videos.take(10).toList();
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return ytVideoToSpotubeTrack(
|
||||||
|
ytVideo,
|
||||||
|
track,
|
||||||
|
noSponsorBlock: noSponsorBlock,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Source> getAppropriateSource(
|
Future<Source> getAppropriateSource(
|
||||||
@ -650,24 +635,20 @@ class Playback extends PersistedChangeNotifier {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
FutureOr<void> loadFromLocal(Map<String, dynamic> map) async {
|
FutureOr<void> loadFromLocal(Map<String, dynamic> map) async {
|
||||||
try {
|
if (map["playlist"] != null) {
|
||||||
if (map["playlist"] != null) {
|
playlist = CurrentPlaylist.fromJson(jsonDecode(map["playlist"]));
|
||||||
playlist = CurrentPlaylist.fromJson(jsonDecode(map["playlist"]));
|
|
||||||
}
|
|
||||||
if (map["track"] != null) {
|
|
||||||
final Map<String, dynamic> trackMap = jsonDecode(map["track"]);
|
|
||||||
// for backwards compatibility
|
|
||||||
if (!trackMap.containsKey("skipSegments")) {
|
|
||||||
trackMap["skipSegments"] = await getSkipSegments(
|
|
||||||
trackMap["id"],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
track = SpotubeTrack.fromJson(trackMap);
|
|
||||||
}
|
|
||||||
volume = map["volume"] ?? volume;
|
|
||||||
} catch (e, stack) {
|
|
||||||
_logger.e("loadFromLocal", e, stack);
|
|
||||||
}
|
}
|
||||||
|
if (map["track"] != null) {
|
||||||
|
final Map<String, dynamic> trackMap = jsonDecode(map["track"]);
|
||||||
|
// for backwards compatibility
|
||||||
|
if (!trackMap.containsKey("skipSegments")) {
|
||||||
|
trackMap["skipSegments"] = await getSkipSegments(
|
||||||
|
trackMap["id"],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
track = SpotubeTrack.fromJson(trackMap);
|
||||||
|
}
|
||||||
|
volume = map["volume"] ?? volume;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -283,44 +283,39 @@ class _MprisMediaPlayer2Player extends DBusObject {
|
|||||||
|
|
||||||
/// Gets value of property org.mpris.MediaPlayer2.Player.Metadata
|
/// Gets value of property org.mpris.MediaPlayer2.Player.Metadata
|
||||||
Future<DBusMethodResponse> getMetadata() async {
|
Future<DBusMethodResponse> getMetadata() async {
|
||||||
try {
|
if (playback.track == null) {
|
||||||
if (playback.track == null) {
|
return DBusMethodSuccessResponse([DBusDict.stringVariant({})]);
|
||||||
return DBusMethodSuccessResponse([DBusDict.stringVariant({})]);
|
|
||||||
}
|
|
||||||
final id = (playback.playlist != null
|
|
||||||
? playback.playlist!.tracks.indexWhere(
|
|
||||||
(track) => playback.track!.id == track.id!,
|
|
||||||
)
|
|
||||||
: 0)
|
|
||||||
.abs();
|
|
||||||
|
|
||||||
return DBusMethodSuccessResponse([
|
|
||||||
DBusDict.stringVariant({
|
|
||||||
"mpris:trackid": DBusString("${path.value}/Track/$id"),
|
|
||||||
"mpris:length": DBusInt32(playback.currentDuration.inMicroseconds),
|
|
||||||
"mpris:artUrl": DBusString(
|
|
||||||
TypeConversionUtils.image_X_UrlString(
|
|
||||||
playback.track?.album?.images,
|
|
||||||
placeholder: ImagePlaceholder.albumArt,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
"xesam:album": DBusString(playback.track!.album!.name!),
|
|
||||||
"xesam:artist": DBusArray.string(
|
|
||||||
playback.track!.artists!.map((artist) => artist.name!),
|
|
||||||
),
|
|
||||||
"xesam:title": DBusString(playback.track!.name!),
|
|
||||||
"xesam:url": DBusString(
|
|
||||||
playback.track is SpotubeTrack
|
|
||||||
? (playback.track as SpotubeTrack).ytUri
|
|
||||||
: playback.track!.previewUrl!,
|
|
||||||
),
|
|
||||||
"xesam:genre": const DBusString("Unknown"),
|
|
||||||
}),
|
|
||||||
]);
|
|
||||||
} catch (e) {
|
|
||||||
print("[DBUS ERROR] $e");
|
|
||||||
rethrow;
|
|
||||||
}
|
}
|
||||||
|
final id = (playback.playlist != null
|
||||||
|
? playback.playlist!.tracks.indexWhere(
|
||||||
|
(track) => playback.track!.id == track.id!,
|
||||||
|
)
|
||||||
|
: 0)
|
||||||
|
.abs();
|
||||||
|
|
||||||
|
return DBusMethodSuccessResponse([
|
||||||
|
DBusDict.stringVariant({
|
||||||
|
"mpris:trackid": DBusString("${path.value}/Track/$id"),
|
||||||
|
"mpris:length": DBusInt32(playback.currentDuration.inMicroseconds),
|
||||||
|
"mpris:artUrl": DBusString(
|
||||||
|
TypeConversionUtils.image_X_UrlString(
|
||||||
|
playback.track?.album?.images,
|
||||||
|
placeholder: ImagePlaceholder.albumArt,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
"xesam:album": DBusString(playback.track!.album!.name!),
|
||||||
|
"xesam:artist": DBusArray.string(
|
||||||
|
playback.track!.artists!.map((artist) => artist.name!),
|
||||||
|
),
|
||||||
|
"xesam:title": DBusString(playback.track!.name!),
|
||||||
|
"xesam:url": DBusString(
|
||||||
|
playback.track is SpotubeTrack
|
||||||
|
? (playback.track as SpotubeTrack).ytUri
|
||||||
|
: playback.track!.previewUrl!,
|
||||||
|
),
|
||||||
|
"xesam:genre": const DBusString("Unknown"),
|
||||||
|
}),
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets value of property org.mpris.MediaPlayer2.Player.Volume
|
/// Gets value of property org.mpris.MediaPlayer2.Player.Volume
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import 'package:catcher/catcher.dart';
|
||||||
|
|
||||||
/// Parses duration string formatted by Duration.toString() to [Duration].
|
/// Parses duration string formatted by Duration.toString() to [Duration].
|
||||||
/// The string should be of form hours:minutes:seconds.microseconds
|
/// The string should be of form hours:minutes:seconds.microseconds
|
||||||
///
|
///
|
||||||
@ -50,7 +52,8 @@ Duration parseDuration(String input) {
|
|||||||
Duration? tryParseDuration(String input) {
|
Duration? tryParseDuration(String input) {
|
||||||
try {
|
try {
|
||||||
return parseDuration(input);
|
return parseDuration(input);
|
||||||
} catch (_) {
|
} catch (e, stack) {
|
||||||
|
Catcher.reportCheckedError(e, stack);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -54,33 +54,28 @@ abstract class ServiceUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static Future<String?> extractLyrics(Uri url) async {
|
static Future<String?> extractLyrics(Uri url) async {
|
||||||
try {
|
final response = await http.get(url);
|
||||||
var response = await http.get(url);
|
|
||||||
|
|
||||||
Document document = parser.parse(response.body);
|
Document document = parser.parse(response.body);
|
||||||
var lyrics = document.querySelector('div.lyrics')?.text.trim();
|
String? lyrics = document.querySelector('div.lyrics')?.text.trim();
|
||||||
if (lyrics == null) {
|
if (lyrics == null) {
|
||||||
lyrics = "";
|
lyrics = "";
|
||||||
document
|
document
|
||||||
.querySelectorAll("div[class^=\"Lyrics__Container\"]")
|
.querySelectorAll("div[class^=\"Lyrics__Container\"]")
|
||||||
.forEach((element) {
|
.forEach((element) {
|
||||||
if (element.text.trim().isNotEmpty) {
|
if (element.text.trim().isNotEmpty) {
|
||||||
var snippet = element.innerHtml.replaceAll("<br>", "\n").replaceAll(
|
final snippet = element.innerHtml.replaceAll("<br>", "\n").replaceAll(
|
||||||
RegExp("<(?!\\s*br\\s*\\/?)[^>]+>", caseSensitive: false),
|
RegExp("<(?!\\s*br\\s*\\/?)[^>]+>", caseSensitive: false),
|
||||||
"",
|
"",
|
||||||
);
|
);
|
||||||
var el = document.createElement("textarea");
|
final el = document.createElement("textarea");
|
||||||
el.innerHtml = snippet;
|
el.innerHtml = snippet;
|
||||||
lyrics = "$lyrics${el.text.trim()}\n\n";
|
lyrics = "$lyrics${el.text.trim()}\n\n";
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
return lyrics;
|
|
||||||
} catch (e, stack) {
|
|
||||||
logger.e("extractLyrics", e, stack);
|
|
||||||
rethrow;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return lyrics;
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<List?> searchSong(
|
static Future<List?> searchSong(
|
||||||
@ -90,36 +85,31 @@ abstract class ServiceUtils {
|
|||||||
bool optimizeQuery = false,
|
bool optimizeQuery = false,
|
||||||
bool authHeader = false,
|
bool authHeader = false,
|
||||||
}) async {
|
}) async {
|
||||||
try {
|
if (apiKey == "" || apiKey == null) {
|
||||||
if (apiKey == "" || apiKey == null) {
|
apiKey = PrimitiveUtils.getRandomElement(lyricsSecrets);
|
||||||
apiKey = PrimitiveUtils.getRandomElement(lyricsSecrets);
|
|
||||||
}
|
|
||||||
const searchUrl = 'https://api.genius.com/search?q=';
|
|
||||||
String song =
|
|
||||||
optimizeQuery ? getTitle(title, artists: artist) : "$title $artist";
|
|
||||||
|
|
||||||
String reqUrl = "$searchUrl${Uri.encodeComponent(song)}";
|
|
||||||
Map<String, String> headers = {"Authorization": 'Bearer $apiKey'};
|
|
||||||
final response = await http.get(
|
|
||||||
Uri.parse(authHeader ? reqUrl : "$reqUrl&access_token=$apiKey"),
|
|
||||||
headers: authHeader ? headers : null,
|
|
||||||
);
|
|
||||||
Map data = jsonDecode(response.body)["response"];
|
|
||||||
if (data["hits"]?.length == 0) return null;
|
|
||||||
List results = data["hits"]?.map((val) {
|
|
||||||
return <String, dynamic>{
|
|
||||||
"id": val["result"]["id"],
|
|
||||||
"full_title": val["result"]["full_title"],
|
|
||||||
"albumArt": val["result"]["song_art_image_url"],
|
|
||||||
"url": val["result"]["url"],
|
|
||||||
"author": val["result"]["primary_artist"]["name"],
|
|
||||||
};
|
|
||||||
}).toList();
|
|
||||||
return results;
|
|
||||||
} catch (e, stack) {
|
|
||||||
logger.e("searchSong", e, stack);
|
|
||||||
rethrow;
|
|
||||||
}
|
}
|
||||||
|
const searchUrl = 'https://api.genius.com/search?q=';
|
||||||
|
String song =
|
||||||
|
optimizeQuery ? getTitle(title, artists: artist) : "$title $artist";
|
||||||
|
|
||||||
|
String reqUrl = "$searchUrl${Uri.encodeComponent(song)}";
|
||||||
|
Map<String, String> headers = {"Authorization": 'Bearer $apiKey'};
|
||||||
|
final response = await http.get(
|
||||||
|
Uri.parse(authHeader ? reqUrl : "$reqUrl&access_token=$apiKey"),
|
||||||
|
headers: authHeader ? headers : null,
|
||||||
|
);
|
||||||
|
Map data = jsonDecode(response.body)["response"];
|
||||||
|
if (data["hits"]?.length == 0) return null;
|
||||||
|
List results = data["hits"]?.map((val) {
|
||||||
|
return <String, dynamic>{
|
||||||
|
"id": val["result"]["id"],
|
||||||
|
"full_title": val["result"]["full_title"],
|
||||||
|
"albumArt": val["result"]["song_art_image_url"],
|
||||||
|
"url": val["result"]["url"],
|
||||||
|
"author": val["result"]["primary_artist"]["name"],
|
||||||
|
};
|
||||||
|
}).toList();
|
||||||
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<String?> getLyrics(
|
static Future<String?> getLyrics(
|
||||||
@ -129,49 +119,44 @@ abstract class ServiceUtils {
|
|||||||
bool optimizeQuery = false,
|
bool optimizeQuery = false,
|
||||||
bool authHeader = false,
|
bool authHeader = false,
|
||||||
}) async {
|
}) async {
|
||||||
try {
|
final results = await searchSong(
|
||||||
final results = await searchSong(
|
title,
|
||||||
title,
|
artists,
|
||||||
artists,
|
apiKey: apiKey,
|
||||||
apiKey: apiKey,
|
optimizeQuery: optimizeQuery,
|
||||||
optimizeQuery: optimizeQuery,
|
authHeader: authHeader,
|
||||||
authHeader: authHeader,
|
);
|
||||||
);
|
if (results == null) return null;
|
||||||
if (results == null) return null;
|
title = getTitle(
|
||||||
title = getTitle(
|
title,
|
||||||
title,
|
artists: artists,
|
||||||
artists: artists,
|
onlyCleanArtist: true,
|
||||||
onlyCleanArtist: true,
|
).trim();
|
||||||
).trim();
|
final ratedLyrics = results.map((result) {
|
||||||
final ratedLyrics = results.map((result) {
|
final gTitle = (result["full_title"] as String).toLowerCase();
|
||||||
final gTitle = (result["full_title"] as String).toLowerCase();
|
int points = 0;
|
||||||
int points = 0;
|
final hasTitle = gTitle.contains(title);
|
||||||
final hasTitle = gTitle.contains(title);
|
final hasAllArtists =
|
||||||
final hasAllArtists =
|
artists.every((artist) => gTitle.contains(artist.toLowerCase()));
|
||||||
artists.every((artist) => gTitle.contains(artist.toLowerCase()));
|
final String lyricAuthor = result["author"].toLowerCase();
|
||||||
final String lyricAuthor = result["author"].toLowerCase();
|
final fromOriginalAuthor =
|
||||||
final fromOriginalAuthor =
|
lyricAuthor.contains(artists.first.toLowerCase());
|
||||||
lyricAuthor.contains(artists.first.toLowerCase());
|
|
||||||
|
|
||||||
for (final criteria in [
|
for (final criteria in [
|
||||||
hasTitle,
|
hasTitle,
|
||||||
hasAllArtists,
|
hasAllArtists,
|
||||||
fromOriginalAuthor,
|
fromOriginalAuthor,
|
||||||
]) {
|
]) {
|
||||||
if (criteria) points++;
|
if (criteria) points++;
|
||||||
}
|
}
|
||||||
return {"result": result, "points": points};
|
return {"result": result, "points": points};
|
||||||
}).sorted(
|
}).sorted(
|
||||||
(a, b) => ((a["points"] as int).compareTo(a["points"] as int)),
|
(a, b) => ((a["points"] as int).compareTo(a["points"] as int)),
|
||||||
);
|
);
|
||||||
final worthyOne = ratedLyrics.first["result"];
|
final worthyOne = ratedLyrics.first["result"];
|
||||||
|
|
||||||
String? lyrics = await extractLyrics(Uri.parse(worthyOne["url"]));
|
String? lyrics = await extractLyrics(Uri.parse(worthyOne["url"]));
|
||||||
return lyrics;
|
return lyrics;
|
||||||
} catch (e, stack) {
|
|
||||||
logger.e("getLyrics", e, stack);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static const baseUri = "https://www.rentanadviser.com/subtitles";
|
static const baseUri = "https://www.rentanadviser.com/subtitles";
|
||||||
@ -263,24 +248,19 @@ abstract class ServiceUtils {
|
|||||||
|
|
||||||
static Future<SpotifySpotubeCredentials> getAccessToken(
|
static Future<SpotifySpotubeCredentials> getAccessToken(
|
||||||
String cookieHeader) async {
|
String cookieHeader) async {
|
||||||
try {
|
final res = await http.get(
|
||||||
final res = await http.get(
|
Uri.parse(
|
||||||
Uri.parse(
|
"https://open.spotify.com/get_access_token?reason=transport&productType=web_player",
|
||||||
"https://open.spotify.com/get_access_token?reason=transport&productType=web_player",
|
),
|
||||||
),
|
headers: {
|
||||||
headers: {
|
"Cookie": cookieHeader,
|
||||||
"Cookie": cookieHeader,
|
"User-Agent":
|
||||||
"User-Agent":
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36"
|
||||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36"
|
},
|
||||||
},
|
);
|
||||||
);
|
return SpotifySpotubeCredentials.fromJson(
|
||||||
return SpotifySpotubeCredentials.fromJson(
|
jsonDecode(res.body),
|
||||||
jsonDecode(res.body),
|
);
|
||||||
);
|
|
||||||
} catch (e, stack) {
|
|
||||||
logger.e("getAccessToken", e, stack);
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void navigate(BuildContext context, String location, {Object? extra}) {
|
static void navigate(BuildContext context, String location, {Object? extra}) {
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
#include "generated_plugin_registrant.h"
|
#include "generated_plugin_registrant.h"
|
||||||
|
|
||||||
#include <audioplayers_linux/audioplayers_linux_plugin.h>
|
#include <audioplayers_linux/audioplayers_linux_plugin.h>
|
||||||
|
#include <catcher/catcher_plugin.h>
|
||||||
#include <metadata_god/metadata_god_plugin.h>
|
#include <metadata_god/metadata_god_plugin.h>
|
||||||
#include <screen_retriever/screen_retriever_plugin.h>
|
#include <screen_retriever/screen_retriever_plugin.h>
|
||||||
#include <url_launcher_linux/url_launcher_plugin.h>
|
#include <url_launcher_linux/url_launcher_plugin.h>
|
||||||
@ -17,6 +18,9 @@ void fl_register_plugins(FlPluginRegistry* registry) {
|
|||||||
g_autoptr(FlPluginRegistrar) audioplayers_linux_registrar =
|
g_autoptr(FlPluginRegistrar) audioplayers_linux_registrar =
|
||||||
fl_plugin_registry_get_registrar_for_plugin(registry, "AudioplayersLinuxPlugin");
|
fl_plugin_registry_get_registrar_for_plugin(registry, "AudioplayersLinuxPlugin");
|
||||||
audioplayers_linux_plugin_register_with_registrar(audioplayers_linux_registrar);
|
audioplayers_linux_plugin_register_with_registrar(audioplayers_linux_registrar);
|
||||||
|
g_autoptr(FlPluginRegistrar) catcher_registrar =
|
||||||
|
fl_plugin_registry_get_registrar_for_plugin(registry, "CatcherPlugin");
|
||||||
|
catcher_plugin_register_with_registrar(catcher_registrar);
|
||||||
g_autoptr(FlPluginRegistrar) metadata_god_registrar =
|
g_autoptr(FlPluginRegistrar) metadata_god_registrar =
|
||||||
fl_plugin_registry_get_registrar_for_plugin(registry, "MetadataGodPlugin");
|
fl_plugin_registry_get_registrar_for_plugin(registry, "MetadataGodPlugin");
|
||||||
metadata_god_plugin_register_with_registrar(metadata_god_registrar);
|
metadata_god_plugin_register_with_registrar(metadata_god_registrar);
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
list(APPEND FLUTTER_PLUGIN_LIST
|
list(APPEND FLUTTER_PLUGIN_LIST
|
||||||
audioplayers_linux
|
audioplayers_linux
|
||||||
|
catcher
|
||||||
metadata_god
|
metadata_god
|
||||||
screen_retriever
|
screen_retriever
|
||||||
url_launcher_linux
|
url_launcher_linux
|
||||||
|
@ -8,10 +8,12 @@ import Foundation
|
|||||||
import audio_service
|
import audio_service
|
||||||
import audio_session
|
import audio_session
|
||||||
import audioplayers_darwin
|
import audioplayers_darwin
|
||||||
|
import catcher
|
||||||
import connectivity_plus_macos
|
import connectivity_plus_macos
|
||||||
|
import device_info_plus
|
||||||
import macos_ui
|
import macos_ui
|
||||||
import metadata_god
|
import metadata_god
|
||||||
import package_info_plus_macos
|
import package_info_plus
|
||||||
import path_provider_foundation
|
import path_provider_foundation
|
||||||
import screen_retriever
|
import screen_retriever
|
||||||
import shared_preferences_foundation
|
import shared_preferences_foundation
|
||||||
@ -24,7 +26,9 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
|||||||
AudioServicePlugin.register(with: registry.registrar(forPlugin: "AudioServicePlugin"))
|
AudioServicePlugin.register(with: registry.registrar(forPlugin: "AudioServicePlugin"))
|
||||||
AudioSessionPlugin.register(with: registry.registrar(forPlugin: "AudioSessionPlugin"))
|
AudioSessionPlugin.register(with: registry.registrar(forPlugin: "AudioSessionPlugin"))
|
||||||
AudioplayersDarwinPlugin.register(with: registry.registrar(forPlugin: "AudioplayersDarwinPlugin"))
|
AudioplayersDarwinPlugin.register(with: registry.registrar(forPlugin: "AudioplayersDarwinPlugin"))
|
||||||
|
CatcherPlugin.register(with: registry.registrar(forPlugin: "CatcherPlugin"))
|
||||||
ConnectivityPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlugin"))
|
ConnectivityPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlugin"))
|
||||||
|
DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin"))
|
||||||
MacOSUiPlugin.register(with: registry.registrar(forPlugin: "MacOSUiPlugin"))
|
MacOSUiPlugin.register(with: registry.registrar(forPlugin: "MacOSUiPlugin"))
|
||||||
MetadataGodPlugin.register(with: registry.registrar(forPlugin: "MetadataGodPlugin"))
|
MetadataGodPlugin.register(with: registry.registrar(forPlugin: "MetadataGodPlugin"))
|
||||||
FLTPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FLTPackageInfoPlusPlugin"))
|
FLTPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FLTPackageInfoPlusPlugin"))
|
||||||
|
97
pubspec.lock
97
pubspec.lock
@ -281,6 +281,15 @@ packages:
|
|||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.2"
|
version: "1.0.2"
|
||||||
|
catcher:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
path: "."
|
||||||
|
ref: HEAD
|
||||||
|
resolved-ref: "5ccf7c0d8bbb07329667cff561aede1a9bee4934"
|
||||||
|
url: "https://github.com/ThexXTURBOXx/catcher"
|
||||||
|
source: git
|
||||||
|
version: "0.7.1"
|
||||||
characters:
|
characters:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -428,6 +437,27 @@ packages:
|
|||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.7.8"
|
version: "0.7.8"
|
||||||
|
device_info_plus:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: device_info_plus
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "8.0.0"
|
||||||
|
device_info_plus_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: device_info_plus_platform_interface
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "7.0.0"
|
||||||
|
dio:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: dio
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "4.0.6"
|
||||||
dots_indicator:
|
dots_indicator:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -599,6 +629,13 @@ packages:
|
|||||||
description: flutter
|
description: flutter
|
||||||
source: sdk
|
source: sdk
|
||||||
version: "0.0.0"
|
version: "0.0.0"
|
||||||
|
flutter_mailer:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: flutter_mailer
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.2"
|
||||||
flutter_plugin_android_lifecycle:
|
flutter_plugin_android_lifecycle:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -637,6 +674,13 @@ packages:
|
|||||||
description: flutter
|
description: flutter
|
||||||
source: sdk
|
source: sdk
|
||||||
version: "0.0.0"
|
version: "0.0.0"
|
||||||
|
fluttertoast:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: fluttertoast
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "8.1.2"
|
||||||
freezed_annotation:
|
freezed_annotation:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -826,6 +870,13 @@ packages:
|
|||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.7.6"
|
version: "1.7.6"
|
||||||
|
mailer:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: mailer
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "6.0.0"
|
||||||
marquee:
|
marquee:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -902,42 +953,14 @@ packages:
|
|||||||
name: package_info_plus
|
name: package_info_plus
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.4.3+1"
|
version: "3.0.2"
|
||||||
package_info_plus_linux:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: package_info_plus_linux
|
|
||||||
url: "https://pub.dartlang.org"
|
|
||||||
source: hosted
|
|
||||||
version: "1.0.5"
|
|
||||||
package_info_plus_macos:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: package_info_plus_macos
|
|
||||||
url: "https://pub.dartlang.org"
|
|
||||||
source: hosted
|
|
||||||
version: "1.3.0"
|
|
||||||
package_info_plus_platform_interface:
|
package_info_plus_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: package_info_plus_platform_interface
|
name: package_info_plus_platform_interface
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.2"
|
version: "2.0.1"
|
||||||
package_info_plus_web:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: package_info_plus_web
|
|
||||||
url: "https://pub.dartlang.org"
|
|
||||||
source: hosted
|
|
||||||
version: "1.0.6"
|
|
||||||
package_info_plus_windows:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: package_info_plus_windows
|
|
||||||
url: "https://pub.dartlang.org"
|
|
||||||
source: hosted
|
|
||||||
version: "2.1.0"
|
|
||||||
palette_generator:
|
palette_generator:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -1185,6 +1208,13 @@ packages:
|
|||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.1"
|
version: "3.0.1"
|
||||||
|
sentry:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: sentry
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "6.19.0"
|
||||||
shared_preferences:
|
shared_preferences:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -1393,6 +1423,13 @@ packages:
|
|||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.1"
|
version: "1.3.1"
|
||||||
|
universal_io:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: universal_io
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.4"
|
||||||
url_launcher:
|
url_launcher:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -18,6 +18,9 @@ dependencies:
|
|||||||
auto_size_text: ^3.0.0
|
auto_size_text: ^3.0.0
|
||||||
badges: ^2.0.3
|
badges: ^2.0.3
|
||||||
cached_network_image: ^3.2.2
|
cached_network_image: ^3.2.2
|
||||||
|
catcher:
|
||||||
|
git:
|
||||||
|
url: https://github.com/ThexXTURBOXx/catcher
|
||||||
collection: ^1.15.0
|
collection: ^1.15.0
|
||||||
cupertino_icons: ^1.0.5
|
cupertino_icons: ^1.0.5
|
||||||
dbus: ^0.7.8
|
dbus: ^0.7.8
|
||||||
@ -47,7 +50,7 @@ dependencies:
|
|||||||
marquee: ^2.2.3
|
marquee: ^2.2.3
|
||||||
metadata_god: ^0.3.2
|
metadata_god: ^0.3.2
|
||||||
mime: ^1.0.2
|
mime: ^1.0.2
|
||||||
package_info_plus: ^1.4.3
|
package_info_plus: ^3.0.2
|
||||||
palette_generator: ^0.3.3
|
palette_generator: ^0.3.3
|
||||||
path: ^1.8.0
|
path: ^1.8.0
|
||||||
path_provider: ^2.0.8
|
path_provider: ^2.0.8
|
||||||
@ -85,6 +88,9 @@ dev_dependencies:
|
|||||||
sdk: flutter
|
sdk: flutter
|
||||||
hive_generator: ^2.0.0
|
hive_generator: ^2.0.0
|
||||||
|
|
||||||
|
dependency_overrides:
|
||||||
|
package_info_plus: ^3.0.2
|
||||||
|
|
||||||
flutter:
|
flutter:
|
||||||
uses-material-design: true
|
uses-material-design: true
|
||||||
assets:
|
assets:
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
#include "generated_plugin_registrant.h"
|
#include "generated_plugin_registrant.h"
|
||||||
|
|
||||||
#include <audioplayers_windows/audioplayers_windows_plugin.h>
|
#include <audioplayers_windows/audioplayers_windows_plugin.h>
|
||||||
|
#include <catcher/catcher_plugin.h>
|
||||||
#include <connectivity_plus_windows/connectivity_plus_windows_plugin.h>
|
#include <connectivity_plus_windows/connectivity_plus_windows_plugin.h>
|
||||||
#include <metadata_god/metadata_god_plugin_c_api.h>
|
#include <metadata_god/metadata_god_plugin_c_api.h>
|
||||||
#include <permission_handler_windows/permission_handler_windows_plugin.h>
|
#include <permission_handler_windows/permission_handler_windows_plugin.h>
|
||||||
@ -18,6 +19,8 @@
|
|||||||
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
||||||
AudioplayersWindowsPluginRegisterWithRegistrar(
|
AudioplayersWindowsPluginRegisterWithRegistrar(
|
||||||
registry->GetRegistrarForPlugin("AudioplayersWindowsPlugin"));
|
registry->GetRegistrarForPlugin("AudioplayersWindowsPlugin"));
|
||||||
|
CatcherPluginRegisterWithRegistrar(
|
||||||
|
registry->GetRegistrarForPlugin("CatcherPlugin"));
|
||||||
ConnectivityPlusWindowsPluginRegisterWithRegistrar(
|
ConnectivityPlusWindowsPluginRegisterWithRegistrar(
|
||||||
registry->GetRegistrarForPlugin("ConnectivityPlusWindowsPlugin"));
|
registry->GetRegistrarForPlugin("ConnectivityPlusWindowsPlugin"));
|
||||||
MetadataGodPluginCApiRegisterWithRegistrar(
|
MetadataGodPluginCApiRegisterWithRegistrar(
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
list(APPEND FLUTTER_PLUGIN_LIST
|
list(APPEND FLUTTER_PLUGIN_LIST
|
||||||
audioplayers_windows
|
audioplayers_windows
|
||||||
|
catcher
|
||||||
connectivity_plus_windows
|
connectivity_plus_windows
|
||||||
metadata_god
|
metadata_god
|
||||||
permission_handler_windows
|
permission_handler_windows
|
||||||
|
Loading…
Reference in New Issue
Block a user