🎨 Prefer single quote
This commit is contained in:
@ -2,12 +2,12 @@ import 'package:spotify/spotify.dart';
|
||||
|
||||
extension ArtistSimpleExtension on List<ArtistSimple> {
|
||||
String asString() {
|
||||
return map((e) => e.name?.replaceAll(",", " ")).join(", ");
|
||||
return map((e) => e.name?.replaceAll(',', ' ')).join(', ');
|
||||
}
|
||||
}
|
||||
|
||||
extension ArtistExtension on List<Artist> {
|
||||
String asString() {
|
||||
return map((e) => e.name?.replaceAll(",", " ")).join(", ");
|
||||
return map((e) => e.name?.replaceAll(',', ' ')).join(', ');
|
||||
}
|
||||
}
|
||||
|
@ -34,7 +34,7 @@ class RhythmMedia extends mk.Media {
|
||||
: "http://${PlatformInfo.isWindows ? "localhost" : InternetAddress.anyIPv4.address}:$serverPort/stream/${track.id}",
|
||||
extras: {
|
||||
...?extras,
|
||||
"track": switch (track) {
|
||||
'track': switch (track) {
|
||||
LocalTrack() => track.toJson(),
|
||||
SourcedTrack() => track.toJson(),
|
||||
_ => track.toJson(),
|
||||
@ -50,14 +50,14 @@ class RhythmMedia extends mk.Media {
|
||||
LocalTrack() => super.uri,
|
||||
_ =>
|
||||
"http://${PlatformInfo.isWindows ? "localhost" : InternetAddress.anyIPv4.address}:"
|
||||
"$serverPort/stream/${track.id}",
|
||||
'$serverPort/stream/${track.id}',
|
||||
};
|
||||
}
|
||||
|
||||
factory RhythmMedia.fromMedia(mk.Media media) {
|
||||
final track = media.uri.startsWith("http")
|
||||
? Track.fromJson(media.extras?["track"])
|
||||
: LocalTrack.fromJson(media.extras?["track"]);
|
||||
final track = media.uri.startsWith('http')
|
||||
? Track.fromJson(media.extras?['track'])
|
||||
: LocalTrack.fromJson(media.extras?['track']);
|
||||
return RhythmMedia(
|
||||
track,
|
||||
extras: media.extras,
|
||||
@ -87,12 +87,12 @@ abstract class AudioPlayerInterface {
|
||||
AudioPlayerInterface()
|
||||
: _mkPlayer = CustomPlayer(
|
||||
configuration: const mk.PlayerConfiguration(
|
||||
title: "Rhythm",
|
||||
title: 'Rhythm',
|
||||
logLevel: kDebugMode ? mk.MPVLogLevel.info : mk.MPVLogLevel.error,
|
||||
),
|
||||
) {
|
||||
_mkPlayer.stream.error.listen((event) {
|
||||
log("[Playback] Error: $event");
|
||||
log('[Playback] Error: $event');
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -19,14 +19,14 @@ class CustomPlayer extends Player {
|
||||
|
||||
bool _shuffled;
|
||||
int _androidAudioSessionId = 0;
|
||||
String _packageName = "";
|
||||
String _packageName = '';
|
||||
AndroidAudioManager? _androidAudioManager;
|
||||
|
||||
CustomPlayer({super.configuration})
|
||||
: _playerStateStream = StreamController.broadcast(),
|
||||
_shuffleStream = StreamController.broadcast(),
|
||||
_shuffled = false {
|
||||
nativePlayer.setProperty("network-timeout", "120");
|
||||
nativePlayer.setProperty('network-timeout', '120');
|
||||
|
||||
_subscriptions = [
|
||||
stream.buffering.listen((event) {
|
||||
@ -63,10 +63,10 @@ class CustomPlayer extends Player {
|
||||
notifyAudioSessionUpdate(true);
|
||||
|
||||
await nativePlayer.setProperty(
|
||||
"audiotrack-session-id",
|
||||
'audiotrack-session-id',
|
||||
_androidAudioSessionId.toString(),
|
||||
);
|
||||
await nativePlayer.setProperty("ao", "audiotrack,opensles,");
|
||||
await nativePlayer.setProperty('ao', 'audiotrack,opensles,');
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -76,11 +76,11 @@ class CustomPlayer extends Player {
|
||||
sendBroadcast(
|
||||
BroadcastMessage(
|
||||
name: active
|
||||
? "android.media.action.OPEN_AUDIO_EFFECT_CONTROL_SESSION"
|
||||
: "android.media.action.CLOSE_AUDIO_EFFECT_CONTROL_SESSION",
|
||||
? 'android.media.action.OPEN_AUDIO_EFFECT_CONTROL_SESSION'
|
||||
: 'android.media.action.CLOSE_AUDIO_EFFECT_CONTROL_SESSION',
|
||||
data: {
|
||||
"android.media.extra.AUDIO_SESSION": _androidAudioSessionId,
|
||||
"android.media.extra.PACKAGE_NAME": _packageName
|
||||
'android.media.extra.AUDIO_SESSION': _androidAudioSessionId,
|
||||
'android.media.extra.PACKAGE_NAME': _packageName
|
||||
},
|
||||
),
|
||||
);
|
||||
|
@ -27,9 +27,9 @@ class AudioServices with WidgetsBindingObserver {
|
||||
: 'dev.solsynth.rhythmBox',
|
||||
androidNotificationChannelName: 'RhythmBox',
|
||||
androidNotificationOngoing: false,
|
||||
androidNotificationIcon: "drawable/ic_launcher_monochrome",
|
||||
androidNotificationIcon: 'drawable/ic_launcher_monochrome',
|
||||
androidStopForegroundOnPause: false,
|
||||
androidNotificationChannelDescription: "RhythmBox Music",
|
||||
androidNotificationChannelDescription: 'RhythmBox Music',
|
||||
),
|
||||
)
|
||||
: null;
|
||||
@ -42,9 +42,9 @@ class AudioServices with WidgetsBindingObserver {
|
||||
await smtc?.addTrack(track);
|
||||
mobile?.addItem(MediaItem(
|
||||
id: track.id!,
|
||||
album: track.album?.name ?? "",
|
||||
album: track.album?.name ?? '',
|
||||
title: track.name!,
|
||||
artist: (track.artists)?.asString() ?? "",
|
||||
artist: (track.artists)?.asString() ?? '',
|
||||
duration: track is SourcedTrack
|
||||
? track.sourceInfo.duration
|
||||
: Duration(milliseconds: track.durationMs ?? 0),
|
||||
|
@ -81,9 +81,9 @@ class WindowsAudioService {
|
||||
await smtc.updateMetadata(
|
||||
MusicMetadata(
|
||||
title: track.name!,
|
||||
albumArtist: track.artists?.firstOrNull?.name ?? "Unknown",
|
||||
artist: track.artists?.asString() ?? "Unknown",
|
||||
album: track.album?.name ?? "Unknown",
|
||||
albumArtist: track.artists?.firstOrNull?.name ?? 'Unknown',
|
||||
artist: track.artists?.asString() ?? 'Unknown',
|
||||
album: track.album?.name ?? 'Unknown',
|
||||
thumbnail: (track.album?.images).asUrlString(),
|
||||
),
|
||||
);
|
||||
|
@ -3,7 +3,7 @@ import 'package:uuid/uuid.dart';
|
||||
|
||||
abstract class PrimitiveUtils {
|
||||
static bool containsTextInBracket(String matcher, String text) {
|
||||
final allMatches = RegExp(r"(?<=\().+?(?=\))").allMatches(matcher);
|
||||
final allMatches = RegExp(r'(?<=\().+?(?=\))').allMatches(matcher);
|
||||
if (allMatches.isEmpty) return false;
|
||||
return allMatches
|
||||
.map((e) => e.group(0))
|
||||
@ -19,13 +19,13 @@ abstract class PrimitiveUtils {
|
||||
|
||||
static String toReadableNumber(double num) {
|
||||
if (num > 999 && num < 99999) {
|
||||
return "${(num / 1000).toStringAsFixed(0)}K";
|
||||
return '${(num / 1000).toStringAsFixed(0)}K';
|
||||
} else if (num > 99999 && num < 999999) {
|
||||
return "${(num / 1000).toStringAsFixed(0)}K";
|
||||
return '${(num / 1000).toStringAsFixed(0)}K';
|
||||
} else if (num > 999999 && num < 999999999) {
|
||||
return "${(num / 1000000).toStringAsFixed(0)}M";
|
||||
return '${(num / 1000000).toStringAsFixed(0)}M';
|
||||
} else if (num > 999999999) {
|
||||
return "${(num / 1000000000).toStringAsFixed(0)}B";
|
||||
return '${(num / 1000000000).toStringAsFixed(0)}B';
|
||||
} else {
|
||||
return num.toStringAsFixed(0);
|
||||
}
|
||||
|
@ -27,7 +27,7 @@ class RhythmMedia extends mk.Media {
|
||||
: "http://${PlatformInfo.isWindows ? "localhost" : InternetAddress.anyIPv4.address}:$serverPort/stream/${track.id}",
|
||||
extras: {
|
||||
...?extras,
|
||||
"track": switch (track) {
|
||||
'track': switch (track) {
|
||||
LocalTrack() => track.toJson(),
|
||||
SourcedTrack() => track.toJson(),
|
||||
_ => track.toJson(),
|
||||
@ -43,14 +43,14 @@ class RhythmMedia extends mk.Media {
|
||||
LocalTrack() => super.uri,
|
||||
_ =>
|
||||
"http://${PlatformInfo.isWindows ? "localhost" : InternetAddress.anyIPv4.address}:"
|
||||
"$serverPort/stream/${track.id}",
|
||||
'$serverPort/stream/${track.id}',
|
||||
};
|
||||
}
|
||||
|
||||
factory RhythmMedia.fromMedia(mk.Media media) {
|
||||
final track = media.uri.startsWith("http")
|
||||
? Track.fromJson(media.extras?["track"])
|
||||
: LocalTrack.fromJson(media.extras?["track"]);
|
||||
final track = media.uri.startsWith('http')
|
||||
? Track.fromJson(media.extras?['track'])
|
||||
: LocalTrack.fromJson(media.extras?['track']);
|
||||
return RhythmMedia(
|
||||
track,
|
||||
extras: media.extras,
|
||||
@ -80,12 +80,12 @@ abstract class AudioPlayerInterface {
|
||||
AudioPlayerInterface()
|
||||
: _mkPlayer = CustomPlayer(
|
||||
configuration: const mk.PlayerConfiguration(
|
||||
title: "Rhythm",
|
||||
title: 'Rhythm',
|
||||
logLevel: kDebugMode ? mk.MPVLogLevel.info : mk.MPVLogLevel.error,
|
||||
),
|
||||
) {
|
||||
_mkPlayer.stream.error.listen((event) {
|
||||
log("[Playback] Error: $event");
|
||||
log('[Playback] Error: $event');
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -31,11 +31,11 @@ class ServerPlaybackRoutesProvider {
|
||||
options: Options(
|
||||
headers: {
|
||||
...request.headers,
|
||||
"User-Agent":
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36",
|
||||
"host": Uri.parse(sourcedTrack.url).host,
|
||||
"Cache-Control": "max-age=0",
|
||||
"Connection": "keep-alive",
|
||||
'User-Agent':
|
||||
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36',
|
||||
'host': Uri.parse(sourcedTrack.url).host,
|
||||
'Cache-Control': 'max-age=0',
|
||||
'Connection': 'keep-alive',
|
||||
},
|
||||
responseType: ResponseType.stream,
|
||||
validateStatus: (status) => status! < 500,
|
||||
@ -54,7 +54,7 @@ class ServerPlaybackRoutesProvider {
|
||||
res.statusCode!,
|
||||
body: audioStream,
|
||||
context: {
|
||||
"shelf.io.buffer_output": false,
|
||||
'shelf.io.buffer_output': false,
|
||||
},
|
||||
headers: res.headers.map,
|
||||
);
|
||||
|
@ -31,9 +31,9 @@ class PlaybackServerProvider extends GetxController {
|
||||
RhythmMedia.serverPort = port;
|
||||
|
||||
_router = Router();
|
||||
_router!.get("/ping", (Request request) => Response.ok("pong"));
|
||||
_router!.get('/ping', (Request request) => Response.ok('pong'));
|
||||
_router!.get(
|
||||
"/stream/<trackId>",
|
||||
'/stream/<trackId>',
|
||||
Get.find<ServerPlaybackRoutesProvider>().getStreamTrackId,
|
||||
);
|
||||
|
||||
|
@ -17,29 +17,29 @@ abstract class SongLinkService {
|
||||
try {
|
||||
final client = GetConnect();
|
||||
final res = await client.get(
|
||||
"https://song.link/s/$spotifyId",
|
||||
'https://song.link/s/$spotifyId',
|
||||
headers: {
|
||||
"Accept":
|
||||
"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8",
|
||||
"User-Agent":
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36"
|
||||
'Accept':
|
||||
'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8',
|
||||
'User-Agent':
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36'
|
||||
},
|
||||
);
|
||||
|
||||
final document = parse(res.body);
|
||||
|
||||
final script = document.getElementById("__NEXT_DATA__")?.text;
|
||||
final script = document.getElementById('__NEXT_DATA__')?.text;
|
||||
|
||||
if (script == null) {
|
||||
return <SongLink>[];
|
||||
}
|
||||
|
||||
final pageProps = jsonDecode(script) as Map<String, dynamic>;
|
||||
final songLinks = pageProps["props"]?["pageProps"]?["pageData"]
|
||||
?["sections"]
|
||||
final songLinks = pageProps['props']?['pageProps']?['pageData']
|
||||
?['sections']
|
||||
?.firstWhere(
|
||||
(section) => section?["sectionId"] == "section|auto|links|listen",
|
||||
)?["links"] as List?;
|
||||
(section) => section?['sectionId'] == 'section|auto|links|listen',
|
||||
)?['links'] as List?;
|
||||
|
||||
return songLinks?.map((link) => SongLink.fromJson(link)).toList() ??
|
||||
<SongLink>[];
|
||||
|
@ -2,7 +2,7 @@ import 'package:rhythm_box/services/sourced_track/models/source_info.dart';
|
||||
import 'package:rhythm_box/services/sourced_track/models/source_map.dart';
|
||||
|
||||
enum SourceCodecs {
|
||||
m4a._("M4a (Best for downloaded music)"),
|
||||
m4a._('M4a (Best for downloaded music)'),
|
||||
weba._("WebA (Best for streamed music)\nDoesn't support audio metadata");
|
||||
|
||||
final String label;
|
||||
|
@ -1,6 +1,6 @@
|
||||
enum SearchMode {
|
||||
youtube._("YouTube"),
|
||||
youtubeMusic._("YouTube Music");
|
||||
youtube._('YouTube'),
|
||||
youtubeMusic._('YouTube Music');
|
||||
|
||||
final String label;
|
||||
|
||||
|
@ -87,7 +87,7 @@ class YoutubeVideoInfo {
|
||||
dislikes: 0,
|
||||
views: searchItem.views,
|
||||
channelName: searchItem.uploaderName,
|
||||
channelId: searchItem.uploaderUrl ?? "",
|
||||
channelId: searchItem.uploaderUrl ?? '',
|
||||
publishedAt: searchItem.uploadedDate != null
|
||||
? DateTime.tryParse(searchItem.uploadedDate!) ?? DateTime(2003, 9, 9)
|
||||
: DateTime(2003, 9, 9),
|
||||
|
@ -41,18 +41,18 @@ abstract class SourcedTrack extends Track {
|
||||
|
||||
static SourcedTrack fromJson(Map<String, dynamic> json) {
|
||||
// TODO Follow user preferences
|
||||
const audioSource = "youtube";
|
||||
const audioSource = 'youtube';
|
||||
|
||||
final sourceInfo = SourceInfo.fromJson(json);
|
||||
final source = SourceMap.fromJson(json);
|
||||
final track = Track.fromJson(json);
|
||||
final siblings = (json["siblings"] as List)
|
||||
final siblings = (json['siblings'] as List)
|
||||
.map((sibling) => SourceInfo.fromJson(sibling))
|
||||
.toList()
|
||||
.cast<SourceInfo>();
|
||||
|
||||
return switch (audioSource) {
|
||||
"piped" => PipedSourcedTrack(
|
||||
'piped' => PipedSourcedTrack(
|
||||
source: source,
|
||||
siblings: siblings,
|
||||
sourceInfo: sourceInfo,
|
||||
@ -87,11 +87,11 @@ abstract class SourcedTrack extends Track {
|
||||
required Track track,
|
||||
}) async {
|
||||
// TODO Follow user preferences
|
||||
const audioSource = "youtube";
|
||||
const audioSource = 'youtube';
|
||||
|
||||
try {
|
||||
return switch (audioSource) {
|
||||
"piped" => await PipedSourcedTrack.fetchFromTrack(track: track),
|
||||
'piped' => await PipedSourcedTrack.fetchFromTrack(track: track),
|
||||
_ => await YoutubeSourcedTrack.fetchFromTrack(track: track),
|
||||
};
|
||||
} on TrackNotFoundError catch (_) {
|
||||
@ -111,10 +111,10 @@ abstract class SourcedTrack extends Track {
|
||||
required Track track,
|
||||
}) {
|
||||
// TODO Follow user preferences
|
||||
const audioSource = "youtube";
|
||||
const audioSource = 'youtube';
|
||||
|
||||
return switch (audioSource) {
|
||||
"piped" => PipedSourcedTrack.fetchSiblings(track: track),
|
||||
'piped' => PipedSourcedTrack.fetchSiblings(track: track),
|
||||
_ => YoutubeSourcedTrack.fetchSiblings(track: track),
|
||||
};
|
||||
}
|
||||
|
@ -97,8 +97,8 @@ class PipedSourcedTrack extends SourcedTrack {
|
||||
info: PipedSourceInfo(
|
||||
id: item.id,
|
||||
artist: item.channelName,
|
||||
artistUrl: "https://www.youtube.com/${item.channelId}",
|
||||
pageUrl: "https://www.youtube.com/watch?v=${item.id}",
|
||||
artistUrl: 'https://www.youtube.com/${item.channelId}',
|
||||
pageUrl: 'https://www.youtube.com/watch?v=${item.id}',
|
||||
thumbnail: item.thumbnailUrl,
|
||||
title: item.title,
|
||||
duration: item.duration,
|
||||
@ -118,7 +118,7 @@ class PipedSourcedTrack extends SourcedTrack {
|
||||
// TODO Allow user search with normal youtube video (`youtube`)
|
||||
const searchMode = SearchMode.youtubeMusic;
|
||||
// TODO Follow user preferences
|
||||
const audioSource = "youtube";
|
||||
const audioSource = 'youtube';
|
||||
|
||||
final query = SourcedTrack.getSearchTerm(track);
|
||||
|
||||
@ -131,7 +131,7 @@ class PipedSourcedTrack extends SourcedTrack {
|
||||
|
||||
// when falling back to piped API make sure to use the YouTube mode
|
||||
const isYouTubeMusic =
|
||||
audioSource != "piped" ? false : searchMode == SearchMode.youtubeMusic;
|
||||
audioSource != 'piped' ? false : searchMode == SearchMode.youtubeMusic;
|
||||
|
||||
if (isYouTubeMusic) {
|
||||
final artists = (track.artists ?? [])
|
||||
|
@ -15,7 +15,7 @@ import 'package:youtube_explode_dart/youtube_explode_dart.dart';
|
||||
|
||||
final youtubeClient = YoutubeExplode();
|
||||
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,
|
||||
);
|
||||
|
||||
@ -62,11 +62,11 @@ class YoutubeSourcedTrack extends SourcedTrack {
|
||||
|
||||
static SourceMap toSourceMap(StreamManifest manifest) {
|
||||
var m4a = manifest.audioOnly
|
||||
.where((audio) => audio.codec.mimeType == "audio/mp4")
|
||||
.where((audio) => audio.codec.mimeType == 'audio/mp4')
|
||||
.sortByBitrate();
|
||||
|
||||
var weba = manifest.audioOnly
|
||||
.where((audio) => audio.codec.mimeType == "audio/webm")
|
||||
.where((audio) => audio.codec.mimeType == 'audio/webm')
|
||||
.sortByBitrate();
|
||||
|
||||
m4a = m4a.isEmpty ? weba.toList() : m4a;
|
||||
@ -96,7 +96,7 @@ class YoutubeSourcedTrack extends SourcedTrack {
|
||||
final manifest =
|
||||
await youtubeClient.videos.streamsClient.getManifest(item.id).timeout(
|
||||
const Duration(seconds: 5),
|
||||
onTimeout: () => throw ClientException("Timeout"),
|
||||
onTimeout: () => throw ClientException('Timeout'),
|
||||
);
|
||||
sourceMap = toSourceMap(manifest);
|
||||
}
|
||||
@ -105,8 +105,8 @@ class YoutubeSourcedTrack extends SourcedTrack {
|
||||
info: YoutubeSourceInfo(
|
||||
id: item.id,
|
||||
artist: item.channelName,
|
||||
artistUrl: "https://www.youtube.com/channel/${item.channelId}",
|
||||
pageUrl: "https://www.youtube.com/watch?v=${item.id}",
|
||||
artistUrl: 'https://www.youtube.com/channel/${item.channelId}',
|
||||
pageUrl: 'https://www.youtube.com/watch?v=${item.id}',
|
||||
thumbnail: item.thumbnailUrl,
|
||||
title: item.title,
|
||||
duration: item.duration,
|
||||
@ -179,7 +179,7 @@ class YoutubeSourcedTrack extends SourcedTrack {
|
||||
required Track track,
|
||||
}) async {
|
||||
final links = await SongLinkService.links(track.id!);
|
||||
final ytLink = links.firstWhereOrNull((link) => link.platform == "youtube");
|
||||
final ytLink = links.firstWhereOrNull((link) => link.platform == 'youtube');
|
||||
|
||||
if (ytLink?.url != null
|
||||
// allows to fetch siblings more results for already sourced track
|
||||
@ -203,7 +203,7 @@ class YoutubeSourcedTrack extends SourcedTrack {
|
||||
final query = SourcedTrack.getSearchTerm(track);
|
||||
|
||||
final searchResults = await youtubeClient.search.search(
|
||||
"$query - Topic",
|
||||
'$query - Topic',
|
||||
filter: TypeFilters.video,
|
||||
);
|
||||
|
||||
@ -240,7 +240,7 @@ class YoutubeSourcedTrack extends SourcedTrack {
|
||||
.getManifest(newSourceInfo.id)
|
||||
.timeout(
|
||||
const Duration(seconds: 5),
|
||||
onTimeout: () => throw ClientException("Timeout"),
|
||||
onTimeout: () => throw ClientException('Timeout'),
|
||||
);
|
||||
|
||||
// TODO Save to cache here
|
||||
|
@ -20,7 +20,7 @@ abstract class ServiceUtils {
|
||||
|
||||
static String clearArtistsOfTitle(String title, List<String> artists) {
|
||||
return title
|
||||
.replaceAll(RegExp(artists.join("|"), caseSensitive: false), "")
|
||||
.replaceAll(RegExp(artists.join('|'), caseSensitive: false), '')
|
||||
.trim();
|
||||
}
|
||||
|
||||
@ -29,13 +29,13 @@ abstract class ServiceUtils {
|
||||
List<String> artists = const [],
|
||||
bool onlyCleanArtist = false,
|
||||
}) {
|
||||
final match = RegExp(r"(?<=\().+?(?=\))").firstMatch(title)?.group(0);
|
||||
final match = RegExp(r'(?<=\().+?(?=\))').firstMatch(title)?.group(0);
|
||||
final artistInBracket =
|
||||
artists.any((artist) => match?.contains(artist) ?? false);
|
||||
|
||||
if (artistInBracket) {
|
||||
title = title.replaceAll(
|
||||
RegExp(" *\\([^)]*\\) *"),
|
||||
RegExp(' *\\([^)]*\\) *'),
|
||||
'',
|
||||
);
|
||||
}
|
||||
@ -47,9 +47,9 @@ abstract class ServiceUtils {
|
||||
|
||||
return "$title ${artists.map((e) => e.replaceAll(",", " ")).join(", ")}"
|
||||
.toLowerCase()
|
||||
.replaceAll(RegExp(r"\s*\[[^\]]*]"), ' ')
|
||||
.replaceAll(RegExp(r"\sfeat\.|\sft\."), ' ')
|
||||
.replaceAll(RegExp(r"\s+"), ' ')
|
||||
.replaceAll(RegExp(r'\s*\[[^\]]*]'), ' ')
|
||||
.replaceAll(RegExp(r'\sfeat\.|\sft\.'), ' ')
|
||||
.replaceAll(RegExp(r'\s+'), ' ')
|
||||
.trim();
|
||||
}
|
||||
|
||||
@ -60,18 +60,18 @@ abstract class ServiceUtils {
|
||||
Document document = parser.parse(response.body);
|
||||
String? lyrics = document.querySelector('div.lyrics')?.text.trim();
|
||||
if (lyrics == null) {
|
||||
lyrics = "";
|
||||
lyrics = '';
|
||||
document
|
||||
.querySelectorAll("div[class^=\"Lyrics__Container\"]")
|
||||
.querySelectorAll('div[class^="Lyrics__Container"]')
|
||||
.forEach((element) {
|
||||
if (element.text.trim().isNotEmpty) {
|
||||
final snippet = element.innerHtml.replaceAll("<br>", "\n").replaceAll(
|
||||
RegExp("<(?!\\s*br\\s*\\/?)[^>]+>", caseSensitive: false),
|
||||
"",
|
||||
final snippet = element.innerHtml.replaceAll('<br>', '\n').replaceAll(
|
||||
RegExp('<(?!\\s*br\\s*\\/?)[^>]+>', caseSensitive: false),
|
||||
'',
|
||||
);
|
||||
final el = document.createElement("textarea");
|
||||
final el = document.createElement('textarea');
|
||||
el.innerHtml = snippet;
|
||||
lyrics = "$lyrics${el.text.trim()}\n\n";
|
||||
lyrics = '$lyrics${el.text.trim()}\n\n';
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -143,16 +143,16 @@ abstract class ServiceUtils {
|
||||
|
||||
static DateTime parseSpotifyAlbumDate(AlbumSimple? album) {
|
||||
if (album == null || album.releaseDate == null) {
|
||||
return DateTime.parse("1975-01-01");
|
||||
return DateTime.parse('1975-01-01');
|
||||
}
|
||||
|
||||
switch (album.releaseDatePrecision ?? DatePrecision.year) {
|
||||
case DatePrecision.day:
|
||||
return DateTime.parse(album.releaseDate!);
|
||||
case DatePrecision.month:
|
||||
return DateTime.parse("${album.releaseDate}-01");
|
||||
return DateTime.parse('${album.releaseDate}-01');
|
||||
case DatePrecision.year:
|
||||
return DateTime.parse("${album.releaseDate}-01-01");
|
||||
return DateTime.parse('${album.releaseDate}-01-01');
|
||||
}
|
||||
}
|
||||
|
||||
@ -162,9 +162,9 @@ abstract class ServiceUtils {
|
||||
..sort((a, b) {
|
||||
switch (sortBy) {
|
||||
case SortBy.ascending:
|
||||
return a.name?.compareTo(b.name ?? "") ?? 0;
|
||||
return a.name?.compareTo(b.name ?? '') ?? 0;
|
||||
case SortBy.descending:
|
||||
return b.name?.compareTo(a.name ?? "") ?? 0;
|
||||
return b.name?.compareTo(a.name ?? '') ?? 0;
|
||||
case SortBy.newest:
|
||||
final aDate = parseSpotifyAlbumDate(a.album);
|
||||
final bDate = parseSpotifyAlbumDate(b.album);
|
||||
@ -177,10 +177,10 @@ abstract class ServiceUtils {
|
||||
return a.durationMs?.compareTo(b.durationMs ?? 0) ?? 0;
|
||||
case SortBy.artist:
|
||||
return a.artists?.first.name
|
||||
?.compareTo(b.artists?.first.name ?? "") ??
|
||||
?.compareTo(b.artists?.first.name ?? '') ??
|
||||
0;
|
||||
case SortBy.album:
|
||||
return a.album?.name?.compareTo(b.album?.name ?? "") ?? 0;
|
||||
return a.album?.name?.compareTo(b.album?.name ?? '') ?? 0;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
|
Reference in New Issue
Block a user