mirror of
https://github.com/KRTirtho/spotube.git
synced 2025-09-13 07:55:18 +00:00
Customizable Track Match Algorigthm
AudioQuality Option
This commit is contained in:
parent
b3b3acdb1e
commit
f3bacad233
@ -43,7 +43,7 @@ class Home extends HookConsumerWidget {
|
||||
@override
|
||||
Widget build(BuildContext context, ref) {
|
||||
final int titleBarDragMaxWidth = useBreakpointValue(
|
||||
md: 72,
|
||||
md: 80,
|
||||
lg: 256,
|
||||
sm: 0,
|
||||
xl: 0,
|
||||
|
@ -9,7 +9,9 @@ import 'package:spotube/components/Settings/About.dart';
|
||||
import 'package:spotube/components/Settings/ColorSchemePickerDialog.dart';
|
||||
import 'package:spotube/components/Settings/SettingsHotkeyTile.dart';
|
||||
import 'package:spotube/components/Shared/PageWindowTitleBar.dart';
|
||||
import 'package:spotube/helpers/search-youtube.dart';
|
||||
import 'package:spotube/models/SpotifyMarkets.dart';
|
||||
import 'package:spotube/models/SpotubeTrack.dart';
|
||||
import 'package:spotube/provider/Auth.dart';
|
||||
import 'package:spotube/provider/UserPreferences.dart';
|
||||
|
||||
@ -201,6 +203,58 @@ class Settings extends HookConsumerWidget {
|
||||
preferences.setCheckUpdate(checked),
|
||||
),
|
||||
),
|
||||
ListTile(
|
||||
title: const Text("Track Match Algorithm"),
|
||||
trailing: DropdownButton<SpotubeTrackMatchAlgorithm>(
|
||||
value: preferences.trackMatchAlgorithm,
|
||||
items: const [
|
||||
DropdownMenuItem(
|
||||
child: Text(
|
||||
"Popular from Author",
|
||||
),
|
||||
value: SpotubeTrackMatchAlgorithm.authenticPopular,
|
||||
),
|
||||
DropdownMenuItem(
|
||||
child: Text(
|
||||
"Accurately Popular",
|
||||
),
|
||||
value: SpotubeTrackMatchAlgorithm.popular,
|
||||
),
|
||||
DropdownMenuItem(
|
||||
child: Text("YouTube's choice is my choice"),
|
||||
value: SpotubeTrackMatchAlgorithm.youtube,
|
||||
),
|
||||
],
|
||||
onChanged: (value) {
|
||||
if (value != null) {
|
||||
preferences.setTrackMatchAlgorithm(value);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
ListTile(
|
||||
title: const Text("Audio Quality"),
|
||||
trailing: DropdownButton<AudioQuality>(
|
||||
value: preferences.audioQuality,
|
||||
items: const [
|
||||
DropdownMenuItem(
|
||||
child: Text(
|
||||
"High",
|
||||
),
|
||||
value: AudioQuality.high,
|
||||
),
|
||||
DropdownMenuItem(
|
||||
child: Text("Low"),
|
||||
value: AudioQuality.low,
|
||||
),
|
||||
],
|
||||
onChanged: (value) {
|
||||
if (value != null) {
|
||||
preferences.setAudioQuality(value);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
if (auth.isLoggedIn)
|
||||
Builder(builder: (context) {
|
||||
Auth auth = ref.watch(authProvider);
|
||||
|
@ -8,9 +8,19 @@ import 'package:youtube_explode_dart/youtube_explode_dart.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:spotube/extensions/list-sort-multiple.dart';
|
||||
|
||||
enum AudioQuality {
|
||||
high,
|
||||
low,
|
||||
}
|
||||
|
||||
final logger = getLogger("toSpotubeTrack");
|
||||
Future<SpotubeTrack> toSpotubeTrack(
|
||||
YoutubeExplode youtube, Track track, String format) async {
|
||||
Future<SpotubeTrack> toSpotubeTrack({
|
||||
required YoutubeExplode youtube,
|
||||
required Track track,
|
||||
required String format,
|
||||
required SpotubeTrackMatchAlgorithm matchAlgorithm,
|
||||
required AudioQuality audioQuality,
|
||||
}) async {
|
||||
final artistsName =
|
||||
track.artists?.map((ar) => ar.name).toList().whereNotNull().toList() ??
|
||||
[];
|
||||
@ -31,7 +41,9 @@ Future<SpotubeTrack> toSpotubeTrack(
|
||||
logger.v("[Youtube Search Term] $queryString");
|
||||
|
||||
VideoSearchList videos = await youtube.search.search(queryString);
|
||||
Video ytVideo;
|
||||
|
||||
if (matchAlgorithm != SpotubeTrackMatchAlgorithm.youtube) {
|
||||
List<Map> ratedRankedVideos = videos
|
||||
.map((video) {
|
||||
// the find should be lazy thus everything case insensitive
|
||||
@ -41,32 +53,20 @@ Future<SpotubeTrack> toSpotubeTrack(
|
||||
(artist) => ytTitle.contains(artist.name!.toLowerCase()),
|
||||
) ??
|
||||
false;
|
||||
final bool authorIsArtist = track.artists?.first.name?.toLowerCase() ==
|
||||
final bool authorIsArtist =
|
||||
track.artists?.first.name?.toLowerCase() ==
|
||||
video.author.toLowerCase();
|
||||
|
||||
final bool hasNoLiveInTitle = !containsTextInBracket(ytTitle, "live");
|
||||
|
||||
// final bool hasOfficialVideo = [
|
||||
// "(official video)",
|
||||
// "[official video]",
|
||||
// "(official music video)",
|
||||
// "[official music video]"
|
||||
// ].any((v) => ytTitle.contains(v));
|
||||
|
||||
// final bool hasOfficialAudio = [
|
||||
// "[official audio]",
|
||||
// "(official audio)",
|
||||
// ].any((v) => ytTitle.contains(v));
|
||||
|
||||
int rate = 0;
|
||||
for (final el in [
|
||||
hasTitle,
|
||||
hasAllArtists,
|
||||
if (matchAlgorithm == SpotubeTrackMatchAlgorithm.authenticPopular)
|
||||
authorIsArtist,
|
||||
hasNoLiveInTitle,
|
||||
!video.isLive,
|
||||
// hasOfficialVideo,
|
||||
// hasOfficialAudio,
|
||||
]) {
|
||||
if (el) rate++;
|
||||
}
|
||||
@ -84,7 +84,10 @@ Future<SpotubeTrack> toSpotubeTrack(
|
||||
["points", "views"],
|
||||
);
|
||||
|
||||
final ytVideo = ratedRankedVideos.first["video"] as Video;
|
||||
ytVideo = ratedRankedVideos.first["video"] as Video;
|
||||
} else {
|
||||
ytVideo = videos.where((video) => !video.isLive).first;
|
||||
}
|
||||
|
||||
final trackManifest = await youtube.videos.streams.getManifest(ytVideo.id);
|
||||
|
||||
@ -92,17 +95,20 @@ Future<SpotubeTrack> toSpotubeTrack(
|
||||
"[YouTube Matched Track] ${ytVideo.title} | ${ytVideo.author} - ${ytVideo.url}",
|
||||
);
|
||||
|
||||
final audioManifest = (Platform.isMacOS || Platform.isIOS)
|
||||
? trackManifest.audioOnly
|
||||
.where((info) => info.codec.mimeType == "audio/mp4")
|
||||
: trackManifest.audioOnly;
|
||||
|
||||
return SpotubeTrack.fromTrack(
|
||||
track: track,
|
||||
ytTrack: ytVideo,
|
||||
// Since Mac OS's & IOS's CodeAudio doesn't support WebMedia
|
||||
// ('audio/webm', 'video/webm' & 'image/webp') thus using 'audio/mpeg'
|
||||
// codec/mimetype for those Platforms
|
||||
ytUri: (Platform.isMacOS || Platform.isIOS
|
||||
? trackManifest.audioOnly
|
||||
.where((info) => info.codec.mimeType == "audio/mp4")
|
||||
.withHighestBitrate()
|
||||
: trackManifest.audioOnly.withHighestBitrate())
|
||||
ytUri: (audioQuality == AudioQuality.high
|
||||
? audioManifest.withHighestBitrate()
|
||||
: audioManifest.sortByBitrate().last)
|
||||
.url
|
||||
.toString(),
|
||||
);
|
||||
|
@ -1,6 +1,15 @@
|
||||
import 'package:spotify/spotify.dart';
|
||||
import 'package:youtube_explode_dart/youtube_explode_dart.dart';
|
||||
|
||||
enum SpotubeTrackMatchAlgorithm {
|
||||
// selects the first result returned from YouTube
|
||||
youtube,
|
||||
// selects the most popular one
|
||||
popular,
|
||||
// selects the most popular one from the author of the track
|
||||
authenticPopular,
|
||||
}
|
||||
|
||||
class SpotubeTrack extends Track {
|
||||
Video ytTrack;
|
||||
String ytUri;
|
||||
|
@ -48,15 +48,19 @@ class Playback extends ChangeNotifier {
|
||||
_init();
|
||||
}
|
||||
|
||||
StreamSubscription<Duration?>? _durationStream;
|
||||
StreamSubscription<Duration>? _positionStream;
|
||||
StreamSubscription<bool>? _playingStream;
|
||||
|
||||
void _init() {
|
||||
player.core.playingStream.listen(
|
||||
_playingStream = player.core.playingStream.listen(
|
||||
(playing) {
|
||||
_isPlaying = playing;
|
||||
notifyListeners();
|
||||
},
|
||||
);
|
||||
|
||||
player.core.durationStream.listen((event) async {
|
||||
_durationStream = player.core.durationStream.listen((event) async {
|
||||
if (event != null) {
|
||||
// Actually things doesn't work all the time as they were
|
||||
// described. So instead of listening to a `_ready`
|
||||
@ -73,6 +77,7 @@ class Playback extends ChangeNotifier {
|
||||
}
|
||||
});
|
||||
|
||||
_positionStream =
|
||||
player.core.createPositionStream().listen((position) async {
|
||||
// detecting multiple same call
|
||||
if (_prevPosition.inSeconds == position.inSeconds) return;
|
||||
@ -97,6 +102,14 @@ class Playback extends ChangeNotifier {
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_positionStream?.cancel();
|
||||
_playingStream?.cancel();
|
||||
_durationStream?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
bool get shuffled => _shuffled;
|
||||
CurrentPlaylist? get currentPlaylist => _currentPlaylist;
|
||||
Track? get currentTrack => _currentTrack;
|
||||
@ -194,9 +207,11 @@ class Playback extends ChangeNotifier {
|
||||
return;
|
||||
}
|
||||
final spotubeTrack = await toSpotubeTrack(
|
||||
youtube,
|
||||
track,
|
||||
preferences.ytSearchFormat,
|
||||
youtube: youtube,
|
||||
track: track,
|
||||
format: preferences.ytSearchFormat,
|
||||
matchAlgorithm: preferences.trackMatchAlgorithm,
|
||||
audioQuality: preferences.audioQuality,
|
||||
);
|
||||
if (setTrackUriById(track.id!, spotubeTrack.ytUri)) {
|
||||
_currentAudioSource = AudioSource.uri(Uri.parse(spotubeTrack.ytUri));
|
||||
|
@ -6,6 +6,8 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:hotkey_manager/hotkey_manager.dart';
|
||||
import 'package:spotube/components/Settings/ColorSchemePickerDialog.dart';
|
||||
import 'package:spotube/helpers/get-random-element.dart';
|
||||
import 'package:spotube/helpers/search-youtube.dart';
|
||||
import 'package:spotube/models/SpotubeTrack.dart';
|
||||
import 'package:spotube/models/generated_secrets.dart';
|
||||
import 'package:spotube/utils/PersistedChangeNotifier.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
@ -19,8 +21,9 @@ class UserPreferences extends PersistedChangeNotifier {
|
||||
HotKey? nextTrackHotKey;
|
||||
HotKey? prevTrackHotKey;
|
||||
HotKey? playPauseHotKey;
|
||||
|
||||
bool checkUpdate;
|
||||
SpotubeTrackMatchAlgorithm trackMatchAlgorithm;
|
||||
AudioQuality audioQuality;
|
||||
|
||||
MaterialColor accentColorScheme;
|
||||
MaterialColor backgroundColorScheme;
|
||||
@ -36,6 +39,8 @@ class UserPreferences extends PersistedChangeNotifier {
|
||||
this.prevTrackHotKey,
|
||||
this.playPauseHotKey,
|
||||
this.checkUpdate = true,
|
||||
this.trackMatchAlgorithm = SpotubeTrackMatchAlgorithm.authenticPopular,
|
||||
this.audioQuality = AudioQuality.high,
|
||||
}) : super();
|
||||
|
||||
void setThemeMode(ThemeMode mode) {
|
||||
@ -104,6 +109,18 @@ class UserPreferences extends PersistedChangeNotifier {
|
||||
updatePersistence();
|
||||
}
|
||||
|
||||
void setTrackMatchAlgorithm(SpotubeTrackMatchAlgorithm algorithm) {
|
||||
trackMatchAlgorithm = algorithm;
|
||||
notifyListeners();
|
||||
updatePersistence();
|
||||
}
|
||||
|
||||
void setAudioQuality(AudioQuality quality) {
|
||||
audioQuality = quality;
|
||||
notifyListeners();
|
||||
updatePersistence();
|
||||
}
|
||||
|
||||
@override
|
||||
FutureOr<void> loadFromLocal(Map<String, dynamic> map) {
|
||||
saveTrackLyrics = map["saveTrackLyrics"] ?? false;
|
||||
@ -128,6 +145,12 @@ class UserPreferences extends PersistedChangeNotifier {
|
||||
accentColorScheme = colorsMap.values
|
||||
.firstWhereOrNull((e) => e.value == map["accentColorScheme"]) ??
|
||||
accentColorScheme;
|
||||
trackMatchAlgorithm = map["trackMatchAlgorithm"] != null
|
||||
? SpotubeTrackMatchAlgorithm.values[map["trackMatchAlgorithm"]]
|
||||
: trackMatchAlgorithm;
|
||||
audioQuality = map["audioQuality"] != null
|
||||
? AudioQuality.values[map["audioQuality"]]
|
||||
: audioQuality;
|
||||
}
|
||||
|
||||
@override
|
||||
@ -150,6 +173,8 @@ class UserPreferences extends PersistedChangeNotifier {
|
||||
"backgroundColorScheme": backgroundColorScheme.value,
|
||||
"accentColorScheme": accentColorScheme.value,
|
||||
"checkUpdate": checkUpdate,
|
||||
"trackMatchAlgorithm": trackMatchAlgorithm.index,
|
||||
"audioQuality": audioQuality.index,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user