🎨 Prefer single quote

This commit is contained in:
2024-08-27 14:48:31 +08:00
parent e7ea852725
commit 95b04adede
31 changed files with 470 additions and 630 deletions

View File

@ -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(', ');
}
}

View File

@ -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');
});
}

View File

@ -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
},
),
);

View File

@ -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),

View File

@ -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(),
),
);

View File

@ -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);
}

View File

@ -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');
});
}

View File

@ -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,
);

View File

@ -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,
);

View File

@ -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>[];

View File

@ -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;

View File

@ -1,6 +1,6 @@
enum SearchMode {
youtube._("YouTube"),
youtubeMusic._("YouTube Music");
youtube._('YouTube'),
youtubeMusic._('YouTube Music');
final String label;

View File

@ -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),

View File

@ -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),
};
}

View File

@ -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 ?? [])

View File

@ -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

View File

@ -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;
}