Playback server

This commit is contained in:
2024-08-27 01:49:05 +08:00
parent 84d66fbc4b
commit 031cab75e0
28 changed files with 2634 additions and 12 deletions

192
lib/collections/assets.gen.dart Executable file
View File

@@ -0,0 +1,192 @@
/// GENERATED CODE - DO NOT MODIFY BY HAND
/// *****************************************************
/// FlutterGen
/// *****************************************************
// coverage:ignore-file
// ignore_for_file: type=lint
// ignore_for_file: directives_ordering,unnecessary_import,implicit_dynamic_list_literal,deprecated_member_use
import 'package:flutter/widgets.dart';
class $AssetsLogosGen {
const $AssetsLogosGen();
/// File path: assets/logos/songlink-transparent.png
AssetGenImage get songlinkTransparent =>
const AssetGenImage('assets/logos/songlink-transparent.png');
/// File path: assets/logos/songlink.png
AssetGenImage get songlink =>
const AssetGenImage('assets/logos/songlink.png');
/// List of all assets
List<AssetGenImage> get values => [songlinkTransparent, songlink];
}
class $AssetsTutorialGen {
const $AssetsTutorialGen();
/// File path: assets/tutorial/step-1.png
AssetGenImage get step1 => const AssetGenImage('assets/tutorial/step-1.png');
/// File path: assets/tutorial/step-2.png
AssetGenImage get step2 => const AssetGenImage('assets/tutorial/step-2.png');
/// File path: assets/tutorial/step-3.png
AssetGenImage get step3 => const AssetGenImage('assets/tutorial/step-3.png');
/// List of all assets
List<AssetGenImage> get values => [step1, step2, step3];
}
class Assets {
Assets._();
static const AssetGenImage albumPlaceholder =
AssetGenImage('assets/album-placeholder.png');
static const AssetGenImage bengaliPatternsBg =
AssetGenImage('assets/bengali-patterns-bg.jpg');
static const AssetGenImage branding = AssetGenImage('assets/branding.png');
static const AssetGenImage emptyBox = AssetGenImage('assets/empty_box.png');
static const AssetGenImage jiosaavn = AssetGenImage('assets/jiosaavn.png');
static const AssetGenImage likedTracks =
AssetGenImage('assets/liked-tracks.jpg');
static const $AssetsLogosGen logos = $AssetsLogosGen();
static const AssetGenImage placeholder =
AssetGenImage('assets/placeholder.png');
static const AssetGenImage rhythm_boxHeroBanner =
AssetGenImage('assets/rhythm_box-hero-banner.png');
static const AssetGenImage rhythm_boxLogoForeground =
AssetGenImage('assets/rhythm_box-logo-foreground.jpg');
static const String rhythm_boxLogoIco = 'assets/rhythm_box-logo.ico';
static const AssetGenImage rhythm_boxLogoPng =
AssetGenImage('assets/rhythm_box-logo.png');
static const String rhythm_boxLogoSvg = 'assets/rhythm_box-logo.svg';
static const AssetGenImage rhythm_boxLogoAndroid12 =
AssetGenImage('assets/rhythm_box-logo_android12.png');
static const AssetGenImage rhythm_boxNightlyLogoForeground =
AssetGenImage('assets/rhythm_box-nightly-logo-foreground.jpg');
static const AssetGenImage rhythm_boxNightlyLogoPng =
AssetGenImage('assets/rhythm_box-nightly-logo.png');
static const String rhythm_boxNightlyLogoSvg =
'assets/rhythm_box-nightly-logo.svg';
static const AssetGenImage rhythm_boxNightlyLogoAndroid12 =
AssetGenImage('assets/rhythm_box-nightly-logo_android12.png');
static const AssetGenImage rhythm_boxScreenshot =
AssetGenImage('assets/rhythm_box-screenshot.png');
static const AssetGenImage rhythm_boxTallCapsule =
AssetGenImage('assets/rhythm_box-tall-capsule.png');
static const AssetGenImage rhythm_boxWideCapsuleLarge =
AssetGenImage('assets/rhythm_box-wide-capsule-large.png');
static const AssetGenImage rhythm_boxWideCapsuleSmall =
AssetGenImage('assets/rhythm_box-wide-capsule-small.png');
static const AssetGenImage rhythm_boxBanner =
AssetGenImage('assets/rhythm_box_banner.png');
static const AssetGenImage success = AssetGenImage('assets/success.png');
static const $AssetsTutorialGen tutorial = $AssetsTutorialGen();
static const AssetGenImage userPlaceholder =
AssetGenImage('assets/user-placeholder.png');
/// List of all assets
static List<dynamic> get values => [
albumPlaceholder,
bengaliPatternsBg,
branding,
emptyBox,
jiosaavn,
likedTracks,
placeholder,
rhythm_boxHeroBanner,
rhythm_boxLogoForeground,
rhythm_boxLogoIco,
rhythm_boxLogoPng,
rhythm_boxLogoSvg,
rhythm_boxLogoAndroid12,
rhythm_boxNightlyLogoForeground,
rhythm_boxNightlyLogoPng,
rhythm_boxNightlyLogoSvg,
rhythm_boxNightlyLogoAndroid12,
rhythm_boxScreenshot,
rhythm_boxTallCapsule,
rhythm_boxWideCapsuleLarge,
rhythm_boxWideCapsuleSmall,
rhythm_boxBanner,
success,
userPlaceholder
];
}
class AssetGenImage {
const AssetGenImage(this._assetName);
final String _assetName;
Image image({
Key? key,
AssetBundle? bundle,
ImageFrameBuilder? frameBuilder,
ImageErrorWidgetBuilder? errorBuilder,
String? semanticLabel,
bool excludeFromSemantics = false,
double? scale,
double? width,
double? height,
Color? color,
Animation<double>? opacity,
BlendMode? colorBlendMode,
BoxFit? fit,
AlignmentGeometry alignment = Alignment.center,
ImageRepeat repeat = ImageRepeat.noRepeat,
Rect? centerSlice,
bool matchTextDirection = false,
bool gaplessPlayback = false,
bool isAntiAlias = false,
String? package,
FilterQuality filterQuality = FilterQuality.low,
int? cacheWidth,
int? cacheHeight,
}) {
return Image.asset(
_assetName,
key: key,
bundle: bundle,
frameBuilder: frameBuilder,
errorBuilder: errorBuilder,
semanticLabel: semanticLabel,
excludeFromSemantics: excludeFromSemantics,
scale: scale,
width: width,
height: height,
color: color,
opacity: opacity,
colorBlendMode: colorBlendMode,
fit: fit,
alignment: alignment,
repeat: repeat,
centerSlice: centerSlice,
matchTextDirection: matchTextDirection,
gaplessPlayback: gaplessPlayback,
isAntiAlias: isAntiAlias,
package: package,
filterQuality: filterQuality,
cacheWidth: cacheWidth,
cacheHeight: cacheHeight,
);
}
ImageProvider provider({
AssetBundle? bundle,
String? package,
}) {
return AssetImage(
_assetName,
bundle: bundle,
package: package,
);
}
String get path => _assetName;
String get keyName => _assetName;
}

View File

@@ -0,0 +1,8 @@
import 'package:intl/intl.dart';
final compactNumberFormatter = NumberFormat.compact();
final usdFormatter = NumberFormat.compactCurrency(
locale: 'en-US',
symbol: r"$",
decimalDigits: 2,
);

232
lib/collections/gradients.dart Executable file
View File

@@ -0,0 +1,232 @@
import 'package:flutter/material.dart';
const gradients = [
LinearGradient(colors: [
Color.fromRGBO(123, 102, 255, 1),
Color.fromRGBO(95, 189, 255, 1),
Color.fromRGBO(150, 239, 255, 1),
Color.fromRGBO(197, 255, 248, 1)
]),
LinearGradient(colors: [
Color.fromRGBO(245, 204, 160, 1),
Color.fromRGBO(228, 143, 69, 1),
Color.fromRGBO(153, 77, 28, 1),
Color.fromRGBO(107, 36, 12, 1)
]),
LinearGradient(colors: [
Color.fromRGBO(243, 243, 243, 1),
Color.fromRGBO(197, 232, 152, 1),
Color.fromRGBO(41, 173, 178, 1),
Color.fromRGBO(7, 102, 173, 1)
]),
LinearGradient(colors: [
Color.fromRGBO(240, 89, 65, 1),
Color.fromRGBO(190, 49, 68, 1),
Color.fromRGBO(135, 35, 65, 1),
Color.fromRGBO(34, 9, 44, 1)
]),
LinearGradient(colors: [
Color.fromRGBO(119, 107, 93, 1),
Color.fromRGBO(176, 166, 149, 1),
Color.fromRGBO(235, 227, 213, 1),
Color.fromRGBO(243, 238, 234, 1)
]),
LinearGradient(colors: [
Color.fromRGBO(208, 162, 247, 1),
Color.fromRGBO(220, 191, 255, 1),
Color.fromRGBO(229, 212, 255, 1),
Color.fromRGBO(241, 234, 255, 1)
]),
LinearGradient(colors: [
Color.fromRGBO(221, 242, 253, 1),
Color.fromRGBO(155, 190, 200, 1),
Color.fromRGBO(66, 125, 157, 1),
Color.fromRGBO(22, 72, 99, 1)
]),
LinearGradient(colors: [
Color.fromRGBO(119, 67, 219, 1),
Color.fromRGBO(195, 172, 208, 1),
Color.fromRGBO(247, 239, 229, 1),
Color.fromRGBO(255, 251, 245, 1)
]),
LinearGradient(colors: [
Color.fromRGBO(194, 217, 255, 1),
Color.fromRGBO(142, 143, 250, 1),
Color.fromRGBO(119, 82, 254, 1),
Color.fromRGBO(25, 4, 130, 1)
]),
LinearGradient(colors: [
Color.fromRGBO(104, 126, 255, 1),
Color.fromRGBO(128, 179, 255, 1),
Color.fromRGBO(152, 228, 255, 1),
Color.fromRGBO(182, 255, 250, 1)
]),
LinearGradient(colors: [
Color.fromRGBO(176, 87, 141, 1),
Color.fromRGBO(217, 136, 185, 1),
Color.fromRGBO(250, 203, 234, 1),
Color.fromRGBO(255, 228, 214, 1)
]),
LinearGradient(colors: [
Color.fromRGBO(190, 255, 247, 1),
Color.fromRGBO(166, 246, 255, 1),
Color.fromRGBO(158, 221, 255, 1),
Color.fromRGBO(100, 153, 233, 1)
]),
LinearGradient(colors: [
Color.fromRGBO(245, 252, 205, 1),
Color.fromRGBO(120, 214, 198, 1),
Color.fromRGBO(65, 145, 151, 1),
Color.fromRGBO(18, 72, 107, 1)
]),
LinearGradient(colors: [
Color.fromRGBO(229, 207, 247, 1),
Color.fromRGBO(157, 118, 193, 1),
Color.fromRGBO(113, 58, 190, 1),
Color.fromRGBO(91, 8, 136, 1)
]),
LinearGradient(colors: [
Color.fromRGBO(249, 222, 201, 1),
Color.fromRGBO(247, 140, 162, 1),
Color.fromRGBO(216, 0, 50, 1),
Color.fromRGBO(61, 12, 17, 1)
]),
LinearGradient(colors: [
Color.fromRGBO(242, 247, 161, 1),
Color.fromRGBO(53, 162, 159, 1),
Color.fromRGBO(8, 131, 149, 1),
Color.fromRGBO(7, 25, 82, 1)
]),
LinearGradient(colors: [
Color.fromRGBO(243, 159, 90, 1),
Color.fromRGBO(174, 68, 90, 1),
Color.fromRGBO(102, 37, 73, 1),
Color.fromRGBO(69, 25, 82, 1)
]),
LinearGradient(colors: [
Color.fromRGBO(255, 200, 200, 1),
Color.fromRGBO(255, 155, 130, 1),
Color.fromRGBO(255, 63, 164, 1),
Color.fromRGBO(87, 55, 93, 1)
]),
LinearGradient(colors: [
Color.fromRGBO(238, 238, 238, 1),
Color.fromRGBO(100, 204, 197, 1),
Color.fromRGBO(23, 107, 135, 1),
Color.fromRGBO(5, 59, 80, 1)
]),
LinearGradient(colors: [
Color.fromRGBO(198, 61, 47, 1),
Color.fromRGBO(226, 94, 62, 1),
Color.fromRGBO(255, 155, 80, 1),
Color.fromRGBO(255, 187, 92, 1)
]),
LinearGradient(colors: [
Color.fromRGBO(236, 83, 176, 1),
Color.fromRGBO(157, 68, 192, 1),
Color.fromRGBO(77, 45, 183, 1),
Color.fromRGBO(14, 33, 160, 1)
]),
LinearGradient(colors: [
Color.fromRGBO(242, 236, 190, 1),
Color.fromRGBO(226, 199, 153, 1),
Color.fromRGBO(192, 130, 97, 1),
Color.fromRGBO(154, 59, 59, 1)
]),
LinearGradient(colors: [
Color.fromRGBO(255, 253, 140, 1),
Color.fromRGBO(151, 255, 244, 1),
Color.fromRGBO(112, 145, 245, 1),
Color.fromRGBO(121, 63, 223, 1)
]),
LinearGradient(colors: [
Color.fromRGBO(67, 83, 52, 1),
Color.fromRGBO(158, 179, 132, 1),
Color.fromRGBO(206, 222, 189, 1),
Color.fromRGBO(250, 241, 228, 1)
]),
LinearGradient(colors: [
Color.fromRGBO(250, 240, 230, 1),
Color.fromRGBO(185, 180, 199, 1),
Color.fromRGBO(92, 84, 112, 1),
Color.fromRGBO(53, 47, 68, 1)
]),
LinearGradient(colors: [
Color.fromRGBO(255, 186, 134, 1),
Color.fromRGBO(246, 99, 92, 1),
Color.fromRGBO(194, 51, 115, 1),
Color.fromRGBO(121, 21, 91, 1)
]),
LinearGradient(colors: [
Color.fromRGBO(213, 255, 208, 1),
Color.fromRGBO(64, 248, 255, 1),
Color.fromRGBO(39, 158, 255, 1),
Color.fromRGBO(12, 53, 106, 1)
]),
LinearGradient(colors: [
Color.fromRGBO(131, 96, 150, 1),
Color.fromRGBO(237, 123, 123, 1),
Color.fromRGBO(240, 184, 110, 1),
Color.fromRGBO(235, 231, 108, 1)
]),
LinearGradient(colors: [
Color.fromRGBO(63, 29, 56, 1),
Color.fromRGBO(77, 60, 119, 1),
Color.fromRGBO(162, 103, 138, 1),
Color.fromRGBO(225, 152, 152, 1)
]),
LinearGradient(colors: [
Color.fromRGBO(254, 123, 229, 1),
Color.fromRGBO(151, 78, 195, 1),
Color.fromRGBO(80, 64, 153, 1),
Color.fromRGBO(49, 56, 102, 1)
]),
LinearGradient(colors: [
Color.fromRGBO(248, 222, 34, 1),
Color.fromRGBO(249, 76, 16, 1),
Color.fromRGBO(199, 0, 57, 1),
Color.fromRGBO(144, 12, 63, 1)
]),
LinearGradient(colors: [
Color.fromRGBO(101, 69, 31, 1),
Color.fromRGBO(118, 88, 39, 1),
Color.fromRGBO(200, 174, 125, 1),
Color.fromRGBO(234, 198, 150, 1)
]),
LinearGradient(colors: [
Color.fromRGBO(255, 246, 224, 1),
Color.fromRGBO(216, 217, 218, 1),
Color.fromRGBO(97, 103, 122, 1),
Color.fromRGBO(39, 40, 41, 1)
]),
LinearGradient(colors: [
Color.fromRGBO(145, 109, 179, 1),
Color.fromRGBO(228, 133, 134, 1),
Color.fromRGBO(252, 186, 173, 1),
Color.fromRGBO(253, 229, 236, 1)
]),
LinearGradient(colors: [
Color.fromRGBO(124, 115, 192, 1),
Color.fromRGBO(148, 173, 215, 1),
Color.fromRGBO(172, 250, 223, 1),
Color.fromRGBO(232, 255, 206, 1)
]),
LinearGradient(colors: [
Color.fromRGBO(174, 216, 204, 1),
Color.fromRGBO(205, 102, 136, 1),
Color.fromRGBO(122, 49, 111, 1),
Color.fromRGBO(70, 25, 89, 1)
]),
LinearGradient(colors: [
Color.fromRGBO(237, 228, 255, 1),
Color.fromRGBO(215, 187, 245, 1),
Color.fromRGBO(160, 118, 249, 1),
Color.fromRGBO(101, 40, 247, 1)
]),
LinearGradient(colors: [
Color.fromRGBO(255, 236, 175, 1),
Color.fromRGBO(255, 176, 127, 1),
Color.fromRGBO(255, 82, 162, 1),
Color.fromRGBO(243, 21, 89, 1)
]),
];

View File

@@ -0,0 +1,26 @@
import 'dart:io';
import 'package:rhythm_box/platform.dart';
import 'package:win32_registry/win32_registry.dart';
Future<void> registerWindowsScheme(String scheme) async {
if (!PlatformInfo.isWindows) return;
String appPath = Platform.resolvedExecutable;
String protocolRegKey = 'Software\\Classes\\$scheme';
RegistryValue protocolRegValue = const RegistryValue(
'URL Protocol',
RegistryValueType.string,
'',
);
String protocolCmdRegKey = 'shell\\open\\command';
RegistryValue protocolCmdRegValue = RegistryValue(
'',
RegistryValueType.string,
'"$appPath" "%1"',
);
final regKey = Registry.currentUser.createKey(protocolRegKey);
regKey.createValue(protocolRegValue);
regKey.createKey(protocolCmdRegKey).createValue(protocolCmdRegValue);
}

88
lib/collections/intents.dart Executable file
View File

@@ -0,0 +1,88 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:go_router/go_router.dart';
import 'package:rhythm_box/platform.dart';
import 'package:rhythm_box/services/audio_player/audio_player.dart';
class PlayPauseIntent extends Intent {
const PlayPauseIntent();
}
class PlayPauseAction extends Action<PlayPauseIntent> {
@override
invoke(intent) async {
if (!audioPlayer.isPlaying) {
await audioPlayer.resume();
} else {
await audioPlayer.pause();
}
return null;
}
}
class NavigationIntent extends Intent {
final GoRouter router;
final String path;
const NavigationIntent(this.router, this.path);
}
class NavigationAction extends Action<NavigationIntent> {
@override
invoke(intent) {
intent.router.go(intent.path);
return null;
}
}
enum HomeTabs {
browse,
search,
library,
lyrics,
}
class HomeTabIntent extends Intent {
final HomeTabs tab;
const HomeTabIntent({required this.tab});
}
class HomeTabAction extends Action<HomeTabIntent> {
@override
invoke(intent) {
return null;
}
}
class SeekIntent extends Intent {
final bool forward;
const SeekIntent(this.forward);
}
class SeekAction extends Action<SeekIntent> {
@override
invoke(intent) async {
final position = audioPlayer.position.inSeconds;
await audioPlayer.seek(
Duration(
seconds: intent.forward ? position + 5 : position - 5,
),
);
return null;
}
}
class CloseAppIntent extends Intent {}
class CloseAppAction extends Action<CloseAppIntent> {
@override
invoke(intent) {
if (PlatformInfo.isDesktop) {
exit(0);
} else {
SystemNavigator.pop();
}
return null;
}
}

View File

@@ -0,0 +1,757 @@
class ISOLanguageName {
final String name;
final String nativeName;
const ISOLanguageName({
required this.name,
required this.nativeName,
});
@override
String toString() {
return "$name ($nativeName)";
}
}
// Uncomment the languages as we add support for them
// Currently supported: bn,en,fr,hi,zh
abstract class LanguageLocals {
static final Map isoLangs = {
// "ab": const ISOLanguageName(
// name: "Abkhaz",
// nativeName: "аҧсуа",
// ),
// "aa": const ISOLanguageName(
// name: "Afar",
// nativeName: "Afaraf",
// ),
// "af": const ISOLanguageName(
// name: "Afrikaans",
// nativeName: "Afrikaans",
// ),
// "ak": const ISOLanguageName(
// name: "Akan",
// nativeName: "Akan",
// ),
// "sq": const ISOLanguageName(
// name: "Albanian",
// nativeName: "Shqip",
// ),
// "am": const ISOLanguageName(
// name: "Amharic",
// nativeName: "አማርኛ",
// ),
"ar": const ISOLanguageName(
name: "Arabic",
nativeName: "العربية",
),
// "an": const ISOLanguageName(
// name: "Aragonese",
// nativeName: "Aragonés",
// ),
// "hy": const ISOLanguageName(
// name: "Armenian",
// nativeName: "Հայերեն",
// ),
// "as": const ISOLanguageName(
// name: "Assamese",
// nativeName: "অসমীয়া",
// ),
// "av": const ISOLanguageName(
// name: "Avaric",
// nativeName: "авар мацӀ, магӀарул мацӀ",
// ),
// "ae": const ISOLanguageName(
// name: "Avestan",
// nativeName: "avesta",
// ),
// "ay": const ISOLanguageName(
// name: "Aymara",
// nativeName: "aymar aru",
// ),
// "az": const ISOLanguageName(
// name: "Azerbaijani",
// nativeName: "azərbaycan dili",
// ),
// "bm": const ISOLanguageName(
// name: "Bambara",
// nativeName: "bamanankan",
// ),
// "ba": const ISOLanguageName(
// name: "Bashkir",
// nativeName: "башҡорт теле",
// ),
"eu": const ISOLanguageName(
name: "Basque",
nativeName: "Euskara",
),
// "be": const ISOLanguageName(
// name: "Belarusian",
// nativeName: "Беларуская",
// ),
"bn": const ISOLanguageName(
name: "Bengali",
nativeName: "বাংলা",
),
// "bh": const ISOLanguageName(
// name: "Bihari",
// nativeName: "भोजपुरी",
// ),
// "bi": const ISOLanguageName(
// name: "Bislama",
// nativeName: "Bislama",
// ),
// "bs": const ISOLanguageName(
// name: "Bosnian",
// nativeName: "bosanski jezik",
// ),
// "br": const ISOLanguageName(
// name: "Breton",
// nativeName: "brezhoneg",
// ),
// "bg": const ISOLanguageName(
// name: "Bulgarian",
// nativeName: "български език",
// ),
// "my": const ISOLanguageName(
// name: "Burmese",
// nativeName: "ဗမာစာ",
// ),
"ca": const ISOLanguageName(
name: "Catalan",
nativeName: "Català",
),
// "ch": const ISOLanguageName(
// name: "Chamorro",
// nativeName: "Chamoru",
// ),
// "ce": const ISOLanguageName(
// name: "Chechen",
// nativeName: "нохчийн мотт",
// ),
// "ny": const ISOLanguageName(
// name: "Chichewa",
// nativeName: "chiCheŵa",
// ),
"zh": const ISOLanguageName(
name: "Simplified Chinese",
nativeName: "简体中文",
),
// "cv": const ISOLanguageName(
// name: "Chuvash",
// nativeName: "чӑваш чӗлхи",
// ),
// "kw": const ISOLanguageName(
// name: "Cornish",
// nativeName: "Kernewek",
// ),
// "co": const ISOLanguageName(
// name: "Corsican",
// nativeName: "lingua corsa",
// ),
// "cr": const ISOLanguageName(
// name: "Cree",
// nativeName: "ᓀᐦᐃᔭᐍᐏᐣ",
// ),
// "hr": const ISOLanguageName(
// name: "Croatian",
// nativeName: "hrvatski",
// ),
"cs": const ISOLanguageName(
name: "Czech",
nativeName: "česky, čeština",
),
// "da": const ISOLanguageName(
// name: "Danish",
// nativeName: "dansk",
// ),
// "dv": const ISOLanguageName(
// name: "Maldivian;",
// nativeName: "ދިވެހި",
// ),
"nl": const ISOLanguageName(
name: "Dutch",
nativeName: "Nederlands",
),
"en": const ISOLanguageName(
name: "English",
nativeName: "English",
),
// "eo": const ISOLanguageName(
// name: "Esperanto",
// nativeName: "Esperanto",
// ),
// "et": const ISOLanguageName(
// name: "Estonian",
// nativeName: "eesti",
// ),
// "ee": const ISOLanguageName(
// name: "Ewe",
// nativeName: "Eʋegbe",
// ),
// "fo": const ISOLanguageName(
// name: "Faroese",
// nativeName: "føroyskt",
// ),
// "fj": const ISOLanguageName(
// name: "Fijian",
// nativeName: "vosa Vakaviti",
// ),
"fi": const ISOLanguageName(
name: "Finnish",
nativeName: "suomi",
),
"fr": const ISOLanguageName(
name: "French",
nativeName: "français",
),
// "ff": const ISOLanguageName(
// name: "Fula; Fulah; Pulaar; Pular",
// nativeName: "Fulfulde, Pulaar, Pular",
// ),
// "gl": const ISOLanguageName(
// name: "Galician",
// nativeName: "Galego",
// ),
"ka": const ISOLanguageName(
name: "Georgian",
nativeName: "ქართული",
),
"de": const ISOLanguageName(
name: "German",
nativeName: "Deutsch",
),
// "el": const ISOLanguageName(
// name: "Greek, Modern",
// nativeName: "Ελληνικά",
// ),
// "gn": const ISOLanguageName(
// name: "Guaraní",
// nativeName: "Avañeẽ",
// ),
// "gu": const ISOLanguageName(
// name: "Gujarati",
// nativeName: "ગુજરાતી",
// ),
// "ht": const ISOLanguageName(
// name: "Haitian; Haitian Creole",
// nativeName: "Kreyòl ayisyen",
// ),
// "ha": const ISOLanguageName(
// name: "Hausa",
// nativeName: "Hausa, هَوُسَ",
// ),
// "he": const ISOLanguageName(
// name: "Hebrew (modern)",
// nativeName: "עברית",
// ),
// "hz": const ISOLanguageName(
// name: "Herero",
// nativeName: "Otjiherero",
// ),
"hi": const ISOLanguageName(
name: "Hindi",
nativeName: "हिन्दी, हिंदी",
),
// "ho": const ISOLanguageName(
// name: "Hiri Motu",
// nativeName: "Hiri Motu",
// ),
// "hu": const ISOLanguageName(
// name: "Hungarian",
// nativeName: "Magyar",
// ),
// "ia": const ISOLanguageName(
// name: "Interlingua",
// nativeName: "Interlingua",
// ),
"id": const ISOLanguageName(
name: "Indonesian",
nativeName: "Bahasa Indonesia",
),
// "ie": const ISOLanguageName(
// name: "Interlingue",
// nativeName: "Occidental",
// ),
// "ga": const ISOLanguageName(
// name: "Irish",
// nativeName: "Gaeilge",
// ),
// "ig": const ISOLanguageName(
// name: "Igbo",
// nativeName: "Asụsụ Igbo",
// ),
// "ik": const ISOLanguageName(
// name: "Inupiaq",
// nativeName: "Iñupiaq, Iñupiatun",
// ),
// "io": const ISOLanguageName(
// name: "Ido",
// nativeName: "Ido",
// ),
// "is": const ISOLanguageName(
// name: "Icelandic",
// nativeName: "Íslenska",
// ),
"it": const ISOLanguageName(
name: "Italian",
nativeName: "Italiano",
),
// "iu": const ISOLanguageName(
// name: "Inuktitut",
// nativeName: "ᐃᓄᒃᑎᑐᑦ",
// ),
"ja": const ISOLanguageName(
name: "Japanese",
nativeName: "日本語",
),
// "jv": const ISOLanguageName(
// name: "Javanese",
// nativeName: "basa Jawa",
// ),
// "kl": const ISOLanguageName(
// name: "Kalaallisut, Greenlandic",
// nativeName: "kalaallisut, kalaallit oqaasii",
// ),
// "kn": const ISOLanguageName(
// name: "Kannada",
// nativeName: "ಕನ್ನಡ",
// ),
// "kr": const ISOLanguageName(
// name: "Kanuri",
// nativeName: "Kanuri",
// ),
// "ks": const ISOLanguageName(
// name: "Kashmiri",
// nativeName: "कश्मीरी, كشميري‎",
// ),
// "kk": const ISOLanguageName(
// name: "Kazakh",
// nativeName: "Қазақ тілі",
// ),
// "km": const ISOLanguageName(
// name: "Khmer",
// nativeName: "ភាសាខ្មែរ",
// ),
// "ki": const ISOLanguageName(
// name: "Kikuyu, Gikuyu",
// nativeName: "Gĩkũyũ",
// ),
// "rw": const ISOLanguageName(
// name: "Kinyarwanda",
// nativeName: "Ikinyarwanda",
// ),
// "ky": const ISOLanguageName(
// name: "Kirghiz, Kyrgyz",
// nativeName: "кыргыз тили",
// ),
// "kv": const ISOLanguageName(
// name: "Komi",
// nativeName: "коми кыв",
// ),
// "kg": const ISOLanguageName(
// name: "Kongo",
// nativeName: "KiKongo",
// ),
"ko": const ISOLanguageName(
name: "Korean",
nativeName: "한국어 (韓國語), 조선말 (朝鮮語)",
),
// "ku": const ISOLanguageName(
// name: "Kurdish",
// nativeName: "Kurdî, كوردی‎",
// ),
// "kj": const ISOLanguageName(
// name: "Kwanyama, Kuanyama",
// nativeName: "Kuanyama",
// ),
// "la": const ISOLanguageName(
// name: "Latin",
// nativeName: "latine, lingua latina",
// ),
// "lb": const ISOLanguageName(
// name: "Luxembourgish, Letzeburgesch",
// nativeName: "Lëtzebuergesch",
// ),
// "lg": const ISOLanguageName(
// name: "Luganda",
// nativeName: "Luganda",
// ),
// "li": const ISOLanguageName(
// name: "Limburgish, Limburgan, Limburger",
// nativeName: "Limburgs",
// ),
// "ln": const ISOLanguageName(
// name: "Lingala",
// nativeName: "Lingála",
// ),
// "lo": const ISOLanguageName(
// name: "Lao",
// nativeName: "ພາສາລາວ",
// ),
// "lt": const ISOLanguageName(
// name: "Lithuanian",
// nativeName: "lietuvių kalba",
// ),
// "lu": const ISOLanguageName(
// name: "Luba-Katanga",
// nativeName: "",
// ),
// "lv": const ISOLanguageName(
// name: "Latvian",
// nativeName: "latviešu valoda",
// ),
// "gv": const ISOLanguageName(
// name: "Manx",
// nativeName: "Gaelg, Gailck",
// ),
// "mk": const ISOLanguageName(
// name: "Macedonian",
// nativeName: "македонски јазик",
// ),
// "mg": const ISOLanguageName(
// name: "Malagasy",
// nativeName: "Malagasy fiteny",
// ),
// "ms": const ISOLanguageName(
// name: "Malay",
// nativeName: "bahasa Melayu, بهاس ملايو‎",
// ),
// "ml": const ISOLanguageName(
// name: "Malayalam",
// nativeName: "മലയാളം",
// ),
// "mt": const ISOLanguageName(
// name: "Maltese",
// nativeName: "Malti",
// ),
// "mi": const ISOLanguageName(
// name: "Māori",
// nativeName: "te reo Māori",
// ),
// "mr": const ISOLanguageName(
// name: "Marathi (Marāṭhī)",
// nativeName: "मराठी",
// ),
// "mh": const ISOLanguageName(
// name: "Marshallese",
// nativeName: "Kajin M̧ajeļ",
// ),
// "mn": const ISOLanguageName(
// name: "Mongolian",
// nativeName: "монгол",
// ),
// "na": const ISOLanguageName(
// name: "Nauru",
// nativeName: "Ekakairũ Naoero",
// ),
// "nv": const ISOLanguageName(
// name: "Navajo, Navaho",
// nativeName: "Diné bizaad, Dinékʼehǰí",
// ),
// "nb": const ISOLanguageName(
// name: "Norwegian Bokmål",
// nativeName: "Norsk bokmål",
// ),
// "nd": const ISOLanguageName(
// name: "North Ndebele",
// nativeName: "isiNdebele",
// ),
"ne": const ISOLanguageName(
name: "Nepali",
nativeName: "नेपाली",
),
// "ng": const ISOLanguageName(
// name: "Ndonga",
// nativeName: "Owambo",
// ),
// "nn": const ISOLanguageName(
// name: "Norwegian Nynorsk",
// nativeName: "Norsk nynorsk",
// ),
// "no": const ISOLanguageName(
// name: "Norwegian",
// nativeName: "Norsk",
// ),
// "ii": const ISOLanguageName(
// name: "Nuosu",
// nativeName: "ꆈꌠ꒿ Nuosuhxop",
// ),
// "nr": const ISOLanguageName(
// name: "South Ndebele",
// nativeName: "isiNdebele",
// ),
// "oc": const ISOLanguageName(
// name: "Occitan",
// nativeName: "Occitan",
// ),
// "oj": const ISOLanguageName(
// name: "Ojibwe, Ojibwa",
// nativeName: "ᐊᓂᔑᓈᐯᒧᐎᓐ",
// ),
// "cu": const ISOLanguageName(
// name: "Old Church Slavonic",
// nativeName: "ѩзыкъ словѣньскъ",
// ),
// "om": const ISOLanguageName(
// name: "Oromo",
// nativeName: "Afaan Oromoo",
// ),
// "or": const ISOLanguageName(
// name: "Oriya",
// nativeName: "ଓଡ଼ିଆ",
// ),
// "os": const ISOLanguageName(
// name: "Ossetian, Ossetic",
// nativeName: "ирон æвзаг",
// ),
// "pa": const ISOLanguageName(
// name: "Panjabi, Punjabi",
// nativeName: "ਪੰਜਾਬੀ, پنجابی‎",
// ),
// "pi": const ISOLanguageName(
// name: "Pāli",
// nativeName: "पाऴि",
// ),
"fa": const ISOLanguageName(
name: "Persian",
nativeName: "فارسی",
),
"pl": const ISOLanguageName(
name: "Polish",
nativeName: "polski",
),
// "ps": const ISOLanguageName(
// name: "Pashto, Pushto",
// nativeName: "پښتو",
// ),
"pt": const ISOLanguageName(
name: "Portuguese",
nativeName: "Português",
),
// "qu": const ISOLanguageName(
// name: "Quechua",
// nativeName: "Runa Simi, Kichwa",
// ),
// "rm": const ISOLanguageName(
// name: "Romansh",
// nativeName: "rumantsch grischun",
// ),
// "rn": const ISOLanguageName(
// name: "Kirundi",
// nativeName: "kiRundi",
// ),
// "ro": const ISOLanguageName(
// name: "Romanian, Moldavian, Moldovan",
// nativeName: "română",
// ),
"ru": const ISOLanguageName(
name: "Russian",
nativeName: "русский язык",
),
// "sa": const ISOLanguageName(
// name: "Sanskrit (Saṁskṛta)",
// nativeName: "संस्कृतम्",
// ),
// "sc": const ISOLanguageName(
// name: "Sardinian",
// nativeName: "sardu",
// ),
// "sd": const ISOLanguageName(
// name: "Sindhi",
// nativeName: "सिन्धी, سنڌي، سندھی‎",
// ),
// "se": const ISOLanguageName(
// name: "Northern Sami",
// nativeName: "Davvisámegiella",
// ),
// "sm": const ISOLanguageName(
// name: "Samoan",
// nativeName: "gagana faa Samoa",
// ),
// "sg": const ISOLanguageName(
// name: "Sango",
// nativeName: "yângâ tî sängö",
// ),
// "sr": const ISOLanguageName(
// name: "Serbian",
// nativeName: "српски језик",
// ),
// "gd": const ISOLanguageName(
// name: "Scottish Gaelic; Gaelic",
// nativeName: "Gàidhlig",
// ),
// "sn": const ISOLanguageName(
// name: "Shona",
// nativeName: "chiShona",
// ),
// "si": const ISOLanguageName(
// name: "Sinhala, Sinhalese",
// nativeName: "සිංහල",
// ),
// "sk": const ISOLanguageName(
// name: "Slovak",
// nativeName: "slovenčina",
// ),
// "sl": const ISOLanguageName(
// name: "Slovene",
// nativeName: "slovenščina",
// ),
// "so": const ISOLanguageName(
// name: "Somali",
// nativeName: "Soomaaliga, af Soomaali",
// ),
// "st": const ISOLanguageName(
// name: "Southern Sotho",
// nativeName: "Sesotho",
// ),
"es": const ISOLanguageName(
name: "Spanish",
nativeName: "español",
),
// "su": const ISOLanguageName(
// name: "Sundanese",
// nativeName: "Basa Sunda",
// ),
// "sw": const ISOLanguageName(
// name: "Swahili",
// nativeName: "Kiswahili",
// ),
// "ss": const ISOLanguageName(
// name: "Swati",
// nativeName: "SiSwati",
// ),
// "sv": const ISOLanguageName(
// name: "Swedish",
// nativeName: "svenska",
// ),
// "ta": const ISOLanguageName(
// name: "Tamil",
// nativeName: "தமிழ்",
// ),
// "te": const ISOLanguageName(
// name: "Telugu",
// nativeName: "తెలుగు",
// ),
// "tg": const ISOLanguageName(
// name: "Tajik",
// nativeName: "тоҷикӣ, toğikī, تاجیکی‎",
// ),
"th": const ISOLanguageName(
name: "Thai",
nativeName: "ไทย",
),
// "ti": const ISOLanguageName(
// name: "Tigrinya",
// nativeName: "ትግርኛ",
// ),
// "bo": const ISOLanguageName(
// name: "Tibetan Standard, Tibetan, Central",
// nativeName: "བོད་ཡིག",
// ),
// "tk": const ISOLanguageName(
// name: "Turkmen",
// nativeName: "Türkmen, Түркмен",
// ),
// "tl": const ISOLanguageName(
// name: "Tagalog",
// nativeName: "Wikang Tagalog, ᜏᜒᜃᜅ᜔ ᜆᜄᜎᜓᜄ᜔",
// ),
// "tn": const ISOLanguageName(
// name: "Tswana",
// nativeName: "Setswana",
// ),
// "to": const ISOLanguageName(
// name: "Tonga (Tonga Islands)",
// nativeName: "faka Tonga",
// ),
"tr": const ISOLanguageName(
name: "Turkish",
nativeName: "Türkçe",
),
// "ts": const ISOLanguageName(
// name: "Tsonga",
// nativeName: "Xitsonga",
// ),
// "tt": const ISOLanguageName(
// name: "Tatar",
// nativeName: "татарча, tatarça, تاتارچا‎",
// ),
// "tw": const ISOLanguageName(
// name: "Twi",
// nativeName: "Twi",
// ),
// "ty": const ISOLanguageName(
// name: "Tahitian",
// nativeName: "Reo Tahiti",
// ),
// "ug": const ISOLanguageName(
// name: "Uighur, Uyghur",
// nativeName: "Uyƣurqə, ئۇيغۇرچە‎",
// ),
"uk": const ISOLanguageName(
name: "Ukrainian",
nativeName: "українська",
),
// "ur": const ISOLanguageName(
// name: "Urdu",
// nativeName: "اردو",
// ),
// "uz": const ISOLanguageName(
// name: "Uzbek",
// nativeName: "zbek, Ўзбек, أۇزبېك‎",
// ),
// "ve": const ISOLanguageName(
// name: "Venda",
// nativeName: "Tshivenḓa",
// ),
"vi": const ISOLanguageName(
name: "Vietnamese",
nativeName: "Tiếng Việt",
),
// "vo": const ISOLanguageName(
// name: "Volapük",
// nativeName: "Volapük",
// ),
// "wa": const ISOLanguageName(
// name: "Walloon",
// nativeName: "Walon",
// ),
// "cy": const ISOLanguageName(
// name: "Welsh",
// nativeName: "Cymraeg",
// ),
// "wo": const ISOLanguageName(
// name: "Wolof",
// nativeName: "Wollof",
// ),
// "fy": const ISOLanguageName(
// name: "Western Frisian",
// nativeName: "Frysk",
// ),
// "xh": const ISOLanguageName(
// name: "Xhosa",
// nativeName: "isiXhosa",
// ),
// "yi": const ISOLanguageName(
// name: "Yiddish",
// nativeName: "ייִדיש",
// ),
// "yo": const ISOLanguageName(
// name: "Yoruba",
// nativeName: "Yorùbá",
// ),
// "za": const ISOLanguageName(
// name: "Zhuang, Chuang",
// nativeName: "Saɯ cueŋƅ, Saw cuengh",
// )
};
static ISOLanguageName getDisplayLanguage(key) {
if (isoLangs.containsKey(key)) {
return isoLangs[key]!;
} else {
throw Exception("Language key incorrect");
}
}
}

View File

@@ -0,0 +1,189 @@
// Country Codes contributed by momobobe <https://github.com/momobobe>
import 'package:spotify/spotify.dart';
final spotifyMarkets = [
(Market.AL, "Albania (AL)"),
(Market.DZ, "Algeria (DZ)"),
(Market.AD, "Andorra (AD)"),
(Market.AO, "Angola (AO)"),
(Market.AG, "Antigua and Barbuda (AG)"),
(Market.AR, "Argentina (AR)"),
(Market.AM, "Armenia (AM)"),
(Market.AU, "Australia (AU)"),
(Market.AT, "Austria (AT)"),
(Market.AZ, "Azerbaijan (AZ)"),
(Market.BH, "Bahrain (BH)"),
(Market.BD, "Bangladesh (BD)"),
(Market.BB, "Barbados (BB)"),
(Market.BY, "Belarus (BY)"),
(Market.BE, "Belgium (BE)"),
(Market.BZ, "Belize (BZ)"),
(Market.BJ, "Benin (BJ)"),
(Market.BT, "Bhutan (BT)"),
(Market.BO, "Bolivia (BO)"),
(Market.BA, "Bosnia and Herzegovina (BA)"),
(Market.BW, "Botswana (BW)"),
(Market.BR, "Brazil (BR)"),
(Market.BN, "Brunei Darussalam (BN)"),
(Market.BG, "Bulgaria (BG)"),
(Market.BF, "Burkina Faso (BF)"),
(Market.BI, "Burundi (BI)"),
(Market.CV, "Cabo Verde / Cape Verde (CV)"),
(Market.KH, "Cambodia (KH)"),
(Market.CM, "Cameroon (CM)"),
(Market.CA, "Canada (CA)"),
(Market.TD, "Chad (TD)"),
(Market.CL, "Chile (CL)"),
(Market.CO, "Colombia (CO)"),
(Market.KM, "Comoros (KM)"),
(Market.CR, "Costa Rica (CR)"),
(Market.HR, "Croatia (HR)"),
(Market.CW, "Curaçao (CW)"),
(Market.CY, "Cyprus (CY)"),
(Market.CZ, "Czech Republic (CZ)"),
(Market.CI, "Ivory Coast (CI)"),
(Market.CD, "Congo (CD)"),
(Market.DK, "Denmark (DK)"),
(Market.DJ, "Djibouti (DJ)"),
(Market.DM, "Dominica (DM)"),
(Market.DO, "Dominican Republic (DO)"),
(Market.EC, "Ecuador (EC)"),
(Market.EG, "Egypt (EG)"),
(Market.SV, "El Salvador (SV)"),
(Market.GQ, "Equatorial Guinea (GQ)"),
(Market.EE, "Estonia (EE)"),
(Market.SZ, "Eswatini (SZ)"),
(Market.FJ, "Fiji (FJ)"),
(Market.FI, "Finland (FI)"),
(Market.FR, "France (FR)"),
(Market.GA, "Gabon (GA)"),
(Market.GE, "Georgia (GE)"),
(Market.DE, "Germany (DE)"),
(Market.GH, "Ghana (GH)"),
(Market.GR, "Greece (GR)"),
(Market.GD, "Grenada (GD)"),
(Market.GT, "Guatemala (GT)"),
(Market.GN, "Guinea (GN)"),
(Market.GW, "Guinea-Bissau (GW)"),
(Market.GY, "Guyana (GY)"),
(Market.HT, "Haiti (HT)"),
(Market.HN, "Honduras (HN)"),
(Market.HK, "Hong Kong (HK)"),
(Market.HU, "Hungary (HU)"),
(Market.IS, "Iceland (IS)"),
(Market.IN, "India (IN)"),
(Market.ID, "Indonesia (ID)"),
(Market.IQ, "Iraq (IQ)"),
(Market.IE, "Ireland (IE)"),
(Market.IL, "Israel (IL)"),
(Market.IT, "Italy (IT)"),
(Market.JM, "Jamaica (JM)"),
(Market.JP, "Japan (JP)"),
(Market.JO, "Jordan (JO)"),
(Market.KZ, "Kazakhstan (KZ)"),
(Market.KE, "Kenya (KE)"),
(Market.KI, "Kiribati (KI)"),
(Market.XK, "Kosovo (XK)"),
(Market.KW, "Kuwait (KW)"),
(Market.KG, "Kyrgyzstan (KG)"),
(Market.LA, "Laos (LA)"),
(Market.LV, "Latvia (LV)"),
(Market.LB, "Lebanon (LB)"),
(Market.LS, "Lesotho (LS)"),
(Market.LR, "Liberia (LR)"),
(Market.LY, "Libya (LY)"),
(Market.LI, "Liechtenstein (LI)"),
(Market.LT, "Lithuania (LT)"),
(Market.LU, "Luxembourg (LU)"),
(Market.MO, "Macao / Macau (MO)"),
(Market.MG, "Madagascar (MG)"),
(Market.MW, "Malawi (MW)"),
(Market.MY, "Malaysia (MY)"),
(Market.MV, "Maldives (MV)"),
(Market.ML, "Mali (ML)"),
(Market.MT, "Malta (MT)"),
(Market.MH, "Marshall Islands (MH)"),
(Market.MR, "Mauritania (MR)"),
(Market.MU, "Mauritius (MU)"),
(Market.MX, "Mexico (MX)"),
(Market.FM, "Micronesia (FM)"),
(Market.MD, "Moldova (MD)"),
(Market.MC, "Monaco (MC)"),
(Market.MN, "Mongolia (MN)"),
(Market.ME, "Montenegro (ME)"),
(Market.MA, "Morocco (MA)"),
(Market.MZ, "Mozambique (MZ)"),
(Market.NA, "Namibia (NA)"),
(Market.NR, "Nauru (NR)"),
(Market.NP, "Nepal (NP)"),
(Market.NL, "Netherlands (NL)"),
(Market.NZ, "New Zealand (NZ)"),
(Market.NI, "Nicaragua (NI)"),
(Market.NE, "Niger (NE)"),
(Market.NG, "Nigeria (NG)"),
(Market.MK, "North Macedonia (MK)"),
(Market.NO, "Norway (NO)"),
(Market.OM, "Oman (OM)"),
(Market.PK, "Pakistan (PK)"),
(Market.PW, "Palau (PW)"),
(Market.PS, "Palestine (PS)"),
(Market.PA, "Panama (PA)"),
(Market.PG, "Papua New Guinea (PG)"),
(Market.PY, "Paraguay (PY)"),
(Market.PE, "Peru (PE)"),
(Market.PH, "Philippines (PH)"),
(Market.PL, "Poland (PL)"),
(Market.PT, "Portugal (PT)"),
(Market.QA, "Qatar (QA)"),
(Market.CG, "Congo (CG)"),
(Market.RO, "Romania (RO)"),
(Market.RU, "Russia (RU)"),
(Market.RW, "Rwanda (RW)"),
(Market.WS, "Samoa (WS)"),
(Market.SM, "San Marino (SM)"),
(Market.SA, "Saudi Arabia (SA)"),
(Market.SN, "Senegal (SN)"),
(Market.RS, "Serbia (RS)"),
(Market.SC, "Seychelles (SC)"),
(Market.SL, "Sierra Leone (SL)"),
(Market.SG, "Singapore (SG)"),
(Market.SK, "Slovakia (SK)"),
(Market.SI, "Slovenia (SI)"),
(Market.SB, "Solomon Islands (SB)"),
(Market.ZA, "South Africa (ZA)"),
(Market.KR, "South Korea (KR)"),
(Market.ES, "Spain (ES)"),
(Market.LK, "Sri Lanka (LK)"),
(Market.KN, "St. Kitts and Nevis (KN)"),
(Market.LC, "St. Lucia (LC)"),
(Market.SR, "Suriname (SR)"),
(Market.SE, "Sweden (SE)"),
(Market.CH, "Switzerland (CH)"),
(Market.ST, "São Tomé and Príncipe (ST)"),
(Market.TW, "Taiwan (TW)"),
(Market.TJ, "Tajikistan (TJ)"),
(Market.TZ, "Tanzania (TZ)"),
(Market.TH, "Thailand (TH)"),
(Market.BS, "The Bahamas (BS)"),
(Market.GM, "The Gambia (GM)"),
(Market.TL, "East Timor (TL)"),
(Market.TG, "Togo (TG)"),
(Market.TO, "Tonga (TO)"),
(Market.TT, "Trinidad and Tobago (TT)"),
(Market.TN, "Tunisia (TN)"),
(Market.TR, "Turkey (TR)"),
(Market.TV, "Tuvalu (TV)"),
(Market.UG, "Uganda (UG)"),
(Market.UA, "Ukraine (UA)"),
(Market.AE, "United Arab Emirates (AE)"),
(Market.GB, "United Kingdom (GB)"),
(Market.US, "United States (US)"),
(Market.UY, "Uruguay (UY)"),
(Market.UZ, "Uzbekistan (UZ)"),
(Market.VU, "Vanuatu (VU)"),
(Market.VE, "Venezuela (VE)"),
(Market.VN, "Vietnam (VN)"),
(Market.ZM, "Zambia (ZM)"),
(Market.ZW, "Zimbabwe (ZW)"),
];

View File

@@ -1,10 +1,18 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:media_kit/media_kit.dart';
import 'package:rhythm_box/providers/audio_player.dart';
import 'package:rhythm_box/providers/spotify.dart';
import 'package:rhythm_box/router.dart';
import 'package:rhythm_box/services/server/active_sourced_track.dart';
import 'package:rhythm_box/services/server/routes/playback.dart';
import 'package:rhythm_box/services/server/server.dart';
import 'package:rhythm_box/services/server/sourced_track.dart';
import 'package:rhythm_box/translations.dart';
void main() {
MediaKit.ensureInitialized();
runApp(const MyApp());
}
@@ -42,5 +50,10 @@ class MyApp extends StatelessWidget {
void _initializeProviders(BuildContext context) async {
Get.lazyPut(() => SpotifyProvider());
Get.lazyPut(() => AudioPlayerProvider());
Get.lazyPut(() => ActiveSourcedTrackProvider());
Get.lazyPut(() => SourcedTrackProvider());
Get.lazyPut(() => ServerPlaybackRoutesProvider());
Get.lazyPut(() => PlaybackServerProvider());
}
}

View File

@@ -0,0 +1,122 @@
import 'dart:math';
import 'package:get/get.dart';
import 'package:media_kit/media_kit.dart' hide Track;
import 'package:rhythm_box/services/audio_player/state.dart';
import 'package:rhythm_box/services/local_track.dart';
import 'package:rhythm_box/services/sourced_track/sourced_track.dart';
import 'package:spotify/spotify.dart' hide Playlist;
import 'package:rhythm_box/services/audio_player/audio_player.dart';
import 'package:shared_preferences/shared_preferences.dart';
class AudioPlayerProvider extends GetxController {
late final SharedPreferences _prefs;
Rx<AudioPlayerState> state = Rx(AudioPlayerState(
playing: false,
shuffled: false,
loopMode: PlaylistMode.none,
playlist: const Playlist([]),
collections: [],
));
AudioPlayerProvider() {
SharedPreferences.getInstance().then((ins) {
_prefs = ins;
});
}
Future<void> _syncSavedState() async {
final data = _prefs.getBool("player_state");
if (data == null) return;
// TODO Serilize and deserilize this state
// TODO Sync saved playlist
}
Future<void> load(
List<Track> tracks, {
int initialIndex = 0,
bool autoPlay = false,
}) async {
final medias = tracks.map((x) => RhythmMedia(x)).toList();
// Giving the initial track a boost so MediaKit won't skip
// because of timeout
final intendedActiveTrack = medias.elementAt(initialIndex);
if (intendedActiveTrack.track is! LocalTrack) {
await SourcedTrack.fetchFromTrack(track: intendedActiveTrack.track);
}
if (medias.isEmpty) return;
await audioPlayer.openPlaylist(
medias.map((s) => s as Media).toList(),
initialIndex: initialIndex,
autoPlay: autoPlay,
);
}
Future<void> addTracksAtFirst(Iterable<Track> tracks) async {
if (state.value.tracks.length == 1) {
return addTracks(tracks);
}
for (int i = 0; i < tracks.length; i++) {
final track = tracks.elementAt(i);
await audioPlayer.addTrackAt(
RhythmMedia(track),
max(state.value.playlist.index, 0) + i + 1,
);
}
}
Future<void> addTrack(Track track) async {
await audioPlayer.addTrack(RhythmMedia(track));
}
Future<void> addTracks(Iterable<Track> tracks) async {
for (final track in tracks) {
await audioPlayer.addTrack(RhythmMedia(track));
}
}
Future<void> removeTrack(String trackId) async {
final index =
state.value.tracks.indexWhere((element) => element.id == trackId);
if (index == -1) return;
await audioPlayer.removeTrack(index);
}
Future<void> removeTracks(Iterable<String> trackIds) async {
for (final trackId in trackIds) {
await removeTrack(trackId);
}
}
Future<void> jumpToTrack(Track track) async {
final index = state.value.tracks
.toList()
.indexWhere((element) => element.id == track.id);
if (index == -1) return;
await audioPlayer.jumpTo(index);
}
Future<void> moveTrack(int oldIndex, int newIndex) async {
if (oldIndex == newIndex ||
newIndex < 0 ||
oldIndex < 0 ||
newIndex > state.value.tracks.length - 1 ||
oldIndex > state.value.tracks.length - 1) return;
await audioPlayer.moveTrack(oldIndex, newIndex);
}
Future<void> stop() async {
await audioPlayer.stop();
}
}

View File

@@ -1,3 +0,0 @@
import 'package:get/get.dart';
class PipedProvider extends GetxController {}

View File

@@ -13,13 +13,6 @@ final router = GoRouter(routes: [
name: "explore",
builder: (context, state) => const ExploreScreen(),
),
GoRoute(
path: "/playlist/:id",
name: "playlistView",
builder: (context, state) => PlaylistViewScreen(
playlistId: state.pathParameters['id']!,
),
),
GoRoute(
path: "/settings",
name: "settings",
@@ -27,4 +20,11 @@ final router = GoRouter(routes: [
),
],
),
GoRoute(
path: "/playlist/:id",
name: "playlistView",
builder: (context, state) => PlaylistViewScreen(
playlistId: state.pathParameters['id']!,
),
),
]);

7
lib/services/artist.dart Normal file
View File

@@ -0,0 +1,7 @@
import 'package:spotify/spotify.dart';
extension ArtistExtension on List<ArtistSimple> {
String asString() {
return map((e) => e.name?.replaceAll(",", " ")).join(", ");
}
}

View File

@@ -0,0 +1,108 @@
import 'package:media_kit/media_kit.dart' hide Track;
import 'package:spotify/spotify.dart' hide Playlist;
import 'package:rhythm_box/services/audio_player/audio_player.dart';
class AudioPlayerState {
final bool playing;
final PlaylistMode loopMode;
final bool shuffled;
final Playlist playlist;
final List<Track> tracks;
final List<String> collections;
AudioPlayerState({
required this.playing,
required this.loopMode,
required this.shuffled,
required this.playlist,
required this.collections,
List<Track>? tracks,
}) : tracks = tracks ??
playlist.medias
.map((media) => RhythmMedia.fromMedia(media).track)
.toList();
factory AudioPlayerState.fromJson(Map<String, dynamic> json) {
return AudioPlayerState(
playing: json['playing'],
loopMode: PlaylistMode.values.firstWhere(
(e) => e.name == json['loopMode'],
orElse: () => audioPlayer.loopMode,
),
shuffled: json['shuffled'],
playlist: Playlist(
json['playlist']['medias']
.map(
(media) => RhythmMedia.fromMedia(Media(
media['uri'],
extras: media['extras'],
httpHeaders: media['httpHeaders'],
)),
)
.cast<Media>()
.toList(),
index: json['playlist']['index'],
),
collections: List<String>.from(json['collections']),
);
}
Map<String, dynamic> toJson() {
return {
'playing': playing,
'loopMode': loopMode.name,
'shuffled': shuffled,
'playlist': {
'medias': playlist.medias
.map((media) => {
'uri': media.uri,
'extras': media.extras,
'httpHeaders': media.httpHeaders,
})
.toList(),
'index': playlist.index,
},
'collections': collections,
};
}
AudioPlayerState copyWith({
bool? playing,
PlaylistMode? loopMode,
bool? shuffled,
Playlist? playlist,
List<String>? collections,
}) {
return AudioPlayerState(
playing: playing ?? this.playing,
loopMode: loopMode ?? this.loopMode,
shuffled: shuffled ?? this.shuffled,
playlist: playlist ?? this.playlist,
collections: collections ?? this.collections,
tracks: playlist == null ? tracks : null,
);
}
Track? get activeTrack {
if (playlist.index == -1) return null;
return tracks.elementAtOrNull(playlist.index);
}
Media? get activeMedia {
if (playlist.index == -1 || playlist.medias.isEmpty) return null;
return playlist.medias.elementAt(playlist.index);
}
bool containsTrack(Track track) {
return tracks.any((t) => t.id == track.id);
}
bool containsTracks(List<Track> tracks) {
return tracks.every(containsTrack);
}
bool containsCollection(String collectionId) {
return collections.contains(collectionId);
}
}

View File

@@ -0,0 +1,84 @@
import 'package:audio_service/audio_service.dart';
import 'package:flutter/material.dart';
import 'package:rhythm_box/platform.dart';
import 'package:rhythm_box/services/audio_services/image.dart';
import 'package:spotify/spotify.dart';
import 'package:rhythm_box/services/audio_services/mobile_audio_service.dart';
import 'package:rhythm_box/services/audio_services/windows_audio_service.dart';
import 'package:rhythm_box/services/sourced_track/sourced_track.dart';
import 'package:rhythm_box/services/artist.dart';
class AudioServices with WidgetsBindingObserver {
final MobileAudioService? mobile;
final WindowsAudioService? smtc;
AudioServices(this.mobile, this.smtc) {
WidgetsBinding.instance.addObserver(this);
}
static Future<AudioServices> create() async {
final mobile =
PlatformInfo.isMobile || PlatformInfo.isMacOS || PlatformInfo.isLinux
? await AudioService.init(
builder: () => MobileAudioService(),
config: AudioServiceConfig(
androidNotificationChannelId: PlatformInfo.isLinux
? 'RhythmBox'
: 'dev.solsynth.rhythmBox',
androidNotificationChannelName: 'RhythmBox',
androidNotificationOngoing: false,
androidNotificationIcon: "drawable/ic_launcher_monochrome",
androidStopForegroundOnPause: false,
androidNotificationChannelDescription: "RhythmBox Music",
),
)
: null;
final smtc = PlatformInfo.isWindows ? WindowsAudioService() : null;
return AudioServices(mobile, smtc);
}
Future<void> addTrack(Track track) async {
await smtc?.addTrack(track);
mobile?.addItem(MediaItem(
id: track.id!,
album: track.album?.name ?? "",
title: track.name!,
artist: (track.artists)?.asString() ?? "",
duration: track is SourcedTrack
? track.sourceInfo.duration
: Duration(milliseconds: track.durationMs ?? 0),
artUri: Uri.parse(
(track.album?.images).asUrlString(
placeholder: ImagePlaceholder.albumArt,
),
),
playable: true,
));
}
void activateSession() {
mobile?.session?.setActive(true);
}
void deactivateSession() {
mobile?.session?.setActive(false);
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
switch (state) {
case AppLifecycleState.detached:
deactivateSession();
mobile?.stop();
break;
default:
break;
}
}
void dispose() {
smtc?.dispose();
WidgetsBinding.instance.removeObserver(this);
}
}

View File

@@ -0,0 +1,34 @@
import 'package:rhythm_box/services/primitive.dart';
import 'package:spotify/spotify.dart';
import 'package:rhythm_box/collections/assets.gen.dart';
import 'package:collection/collection.dart';
enum ImagePlaceholder {
albumArt,
artist,
collection,
online,
}
extension SpotifyImageExtensions on List<Image>? {
String asUrlString({
int index = 1,
required ImagePlaceholder placeholder,
}) {
final String placeholderUrl = {
ImagePlaceholder.albumArt: Assets.albumPlaceholder.path,
ImagePlaceholder.artist: Assets.userPlaceholder.path,
ImagePlaceholder.collection: Assets.placeholder.path,
ImagePlaceholder.online:
"https://avatars.dicebear.com/api/bottts/${PrimitiveUtils.uuid.v4()}.png",
}[placeholder]!;
final sortedImage = this?.sorted((a, b) => a.width!.compareTo(b.width!));
return sortedImage != null && sortedImage.isNotEmpty
? sortedImage[
index > sortedImage.length - 1 ? sortedImage.length - 1 : index]
.url!
: placeholderUrl;
}
}

View File

@@ -0,0 +1,153 @@
import 'dart:async';
import 'package:audio_service/audio_service.dart';
import 'package:audio_session/audio_session.dart';
import 'package:get/get.dart';
import 'package:rhythm_box/providers/audio_player.dart';
import 'package:rhythm_box/services/audio_player/audio_player.dart';
import 'package:media_kit/media_kit.dart' hide Track;
import 'package:rhythm_box/services/audio_player/state.dart';
class MobileAudioService extends BaseAudioHandler {
AudioSession? session;
// ignore: invalid_use_of_protected_member, invalid_use_of_visible_for_testing_member
AudioPlayerState get playlist => Get.find<AudioPlayerProvider>().state.value;
MobileAudioService() {
AudioSession.instance.then((s) {
session = s;
session?.configure(const AudioSessionConfiguration.music());
bool wasPausedByBeginEvent = false;
s.interruptionEventStream.listen((event) async {
if (event.begin) {
switch (event.type) {
case AudioInterruptionType.duck:
await audioPlayer.setVolume(0.5);
break;
case AudioInterruptionType.pause:
case AudioInterruptionType.unknown:
{
wasPausedByBeginEvent = audioPlayer.isPlaying;
await audioPlayer.pause();
break;
}
}
} else {
switch (event.type) {
case AudioInterruptionType.duck:
await audioPlayer.setVolume(1.0);
break;
case AudioInterruptionType.pause when wasPausedByBeginEvent:
case AudioInterruptionType.unknown when wasPausedByBeginEvent:
await audioPlayer.resume();
wasPausedByBeginEvent = false;
break;
default:
break;
}
}
});
s.becomingNoisyEventStream.listen((_) {
audioPlayer.pause();
});
});
audioPlayer.playerStateStream.listen((state) async {
playbackState.add(await _transformEvent());
});
audioPlayer.positionStream.listen((pos) async {
playbackState.add(await _transformEvent());
});
audioPlayer.bufferedPositionStream.listen((pos) async {
playbackState.add(await _transformEvent());
});
}
void addItem(MediaItem item) {
session?.setActive(true);
mediaItem.add(item);
}
@override
Future<void> play() => audioPlayer.resume();
@override
Future<void> pause() => audioPlayer.pause();
@override
Future<void> seek(Duration position) => audioPlayer.seek(position);
@override
Future<void> setShuffleMode(AudioServiceShuffleMode shuffleMode) async {
await super.setShuffleMode(shuffleMode);
audioPlayer.setShuffle(shuffleMode == AudioServiceShuffleMode.all);
}
@override
Future<void> setRepeatMode(AudioServiceRepeatMode repeatMode) async {
super.setRepeatMode(repeatMode);
audioPlayer.setLoopMode(switch (repeatMode) {
AudioServiceRepeatMode.all ||
AudioServiceRepeatMode.group =>
PlaylistMode.loop,
AudioServiceRepeatMode.one => PlaylistMode.single,
_ => PlaylistMode.none,
});
}
@override
Future<void> stop() async {
await Get.find<AudioPlayerProvider>().stop();
}
@override
Future<void> skipToNext() async {
await audioPlayer.skipToNext();
await super.skipToNext();
}
@override
Future<void> skipToPrevious() async {
await audioPlayer.skipToPrevious();
await super.skipToPrevious();
}
@override
Future<void> onTaskRemoved() async {
await Get.find<AudioPlayerProvider>().stop();
return super.onTaskRemoved();
}
Future<PlaybackState> _transformEvent() async {
return PlaybackState(
controls: [
MediaControl.skipToPrevious,
audioPlayer.isPlaying ? MediaControl.pause : MediaControl.play,
MediaControl.skipToNext,
MediaControl.stop,
],
systemActions: {
MediaAction.seek,
},
androidCompactActionIndices: const [0, 1, 2],
playing: audioPlayer.isPlaying,
updatePosition: audioPlayer.position,
bufferedPosition: audioPlayer.bufferedPosition,
shuffleMode: audioPlayer.isShuffled == true
? AudioServiceShuffleMode.all
: AudioServiceShuffleMode.none,
repeatMode: switch (audioPlayer.loopMode) {
PlaylistMode.loop => AudioServiceRepeatMode.all,
PlaylistMode.single => AudioServiceRepeatMode.one,
_ => AudioServiceRepeatMode.none,
},
processingState: audioPlayer.isBuffering
? AudioProcessingState.loading
: AudioProcessingState.ready,
);
}
}

View File

@@ -0,0 +1,101 @@
import 'dart:async';
import 'package:get/get.dart';
import 'package:rhythm_box/providers/audio_player.dart';
import 'package:rhythm_box/services/audio_services/image.dart';
import 'package:smtc_windows/smtc_windows.dart';
import 'package:spotify/spotify.dart';
import 'package:rhythm_box/services/audio_player/audio_player.dart';
import 'package:rhythm_box/services/audio_player/playback_state.dart';
import 'package:rhythm_box/services/artist.dart';
class WindowsAudioService {
final SMTCWindows smtc;
final subscriptions = <StreamSubscription>[];
WindowsAudioService() : smtc = SMTCWindows(enabled: false) {
smtc.setPlaybackStatus(PlaybackStatus.Stopped);
final buttonStream = smtc.buttonPressStream.listen((event) {
switch (event) {
case PressedButton.play:
audioPlayer.resume();
break;
case PressedButton.pause:
audioPlayer.pause();
break;
case PressedButton.next:
audioPlayer.skipToNext();
break;
case PressedButton.previous:
audioPlayer.skipToPrevious();
break;
case PressedButton.stop:
Get.find<AudioPlayerProvider>().stop();
break;
default:
break;
}
});
final playerStateStream =
audioPlayer.playerStateStream.listen((state) async {
switch (state) {
case AudioPlaybackState.playing:
await smtc.setPlaybackStatus(PlaybackStatus.Playing);
break;
case AudioPlaybackState.paused:
await smtc.setPlaybackStatus(PlaybackStatus.Paused);
break;
case AudioPlaybackState.stopped:
await smtc.setPlaybackStatus(PlaybackStatus.Stopped);
break;
case AudioPlaybackState.completed:
await smtc.setPlaybackStatus(PlaybackStatus.Changing);
break;
default:
break;
}
});
final positionStream = audioPlayer.positionStream.listen((pos) async {
await smtc.setPosition(pos);
});
final durationStream = audioPlayer.durationStream.listen((duration) async {
await smtc.setEndTime(duration);
});
subscriptions.addAll([
buttonStream,
playerStateStream,
positionStream,
durationStream,
]);
}
Future<void> addTrack(Track track) async {
if (!smtc.enabled) {
await smtc.enableSmtc();
}
await smtc.updateMetadata(
MusicMetadata(
title: track.name!,
albumArtist: track.artists?.firstOrNull?.name ?? "Unknown",
artist: track.artists?.asString() ?? "Unknown",
album: track.album?.name ?? "Unknown",
thumbnail: (track.album?.images).asUrlString(
placeholder: ImagePlaceholder.albumArt,
),
),
);
}
void dispose() {
smtc.disableSmtc();
smtc.dispose();
for (var element in subscriptions) {
element.cancel();
}
}
}

53
lib/services/primitive.dart Executable file
View File

@@ -0,0 +1,53 @@
import 'dart:math';
import 'package:uuid/uuid.dart';
abstract class PrimitiveUtils {
static bool containsTextInBracket(String matcher, String text) {
final allMatches = RegExp(r"(?<=\().+?(?=\))").allMatches(matcher);
if (allMatches.isEmpty) return false;
return allMatches
.map((e) => e.group(0))
.every((match) => match?.contains(text) ?? false);
}
static final Random _random = Random();
static T getRandomElement<T>(List<T> list) {
return list[_random.nextInt(list.length)];
}
static const uuid = Uuid();
static String toReadableNumber(double num) {
if (num > 999 && num < 99999) {
return "${(num / 1000).toStringAsFixed(0)}K";
} else if (num > 99999 && num < 999999) {
return "${(num / 1000).toStringAsFixed(0)}K";
} else if (num > 999999 && num < 999999999) {
return "${(num / 1000000).toStringAsFixed(0)}M";
} else if (num > 999999999) {
return "${(num / 1000000000).toStringAsFixed(0)}B";
} else {
return num.toStringAsFixed(0);
}
}
static Future<T> raceMultiple<T>(
Future<T> Function() inner, {
Duration timeout = const Duration(milliseconds: 2500),
int retryCount = 4,
}) async {
return Future.any(
List.generate(retryCount, (i) {
if (i == 0) return inner();
return Future.delayed(
Duration(milliseconds: timeout.inMilliseconds * i),
inner,
);
}),
);
}
static String toSafeFileName(String str) {
return str.replaceAll(RegExp(r'[/\?%*:|"<>]'), ' ');
}
}

View File

@@ -0,0 +1,39 @@
import 'package:get/get.dart';
import 'package:rhythm_box/providers/audio_player.dart';
import 'package:rhythm_box/services/audio_player/audio_player.dart';
import 'package:rhythm_box/services/sourced_track/models/source_info.dart';
import 'package:rhythm_box/services/sourced_track/sourced_track.dart';
class ActiveSourcedTrackProvider extends GetxController {
Rx<SourcedTrack?> state = Rx(null);
void updateTrack(SourcedTrack? sourcedTrack) {
state.value = sourcedTrack;
}
Future<void> populateSibling() async {
if (state.value == null) return;
state.value = await state.value!.copyWithSibling();
}
Future<void> swapSibling(SourceInfo sibling) async {
if (state.value == null) return;
await populateSibling();
final newTrack = await state.value!.swapWithSibling(sibling);
if (newTrack == null) return;
state.value = newTrack;
await audioPlayer.pause();
final playback = Get.find<AudioPlayerProvider>();
final oldActiveIndex = audioPlayer.currentIndex;
await playback.addTracksAtFirst([newTrack]);
await Future.delayed(const Duration(milliseconds: 50));
await playback.jumpToTrack(newTrack);
await audioPlayer.removeTrack(oldActiveIndex);
await audioPlayer.resume();
}
}

View File

@@ -0,0 +1,66 @@
import 'dart:developer';
import 'package:dio/dio.dart' hide Response;
import 'package:flutter/foundation.dart';
import 'package:get/get.dart' hide Response;
import 'package:rhythm_box/providers/audio_player.dart';
import 'package:rhythm_box/services/audio_player/audio_player.dart';
import 'package:rhythm_box/services/server/active_sourced_track.dart';
import 'package:rhythm_box/services/server/sourced_track.dart';
import 'package:rhythm_box/services/sourced_track/sourced_track.dart';
import 'package:shelf/shelf.dart';
class ServerPlaybackRoutesProvider {
/// @get('/stream/<trackId>')
Future<Response> getStreamTrackId(Request request, String trackId) async {
final AudioPlayerProvider playback = Get.find();
try {
final track = playback.state.value.tracks
.firstWhere((element) => element.id == trackId);
final ActiveSourcedTrackProvider activeSourcedTrack = Get.find();
final sourcedTrack = activeSourcedTrack.state.value?.id == track.id
? activeSourcedTrack
: await Get.find<SourcedTrackProvider>().fetch(RhythmMedia(track));
activeSourcedTrack.updateTrack(sourcedTrack as SourcedTrack?);
final res = await Dio().get(
sourcedTrack!.url,
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",
},
responseType: ResponseType.stream,
validateStatus: (status) => status! < 500,
),
);
final audioStream =
(res.data?.stream as Stream<Uint8List>?)?.asBroadcastStream();
audioStream!.listen(
(event) {},
cancelOnError: true,
);
return Response(
res.statusCode!,
body: audioStream,
context: {
"shelf.io.buffer_output": false,
},
headers: res.headers.map,
);
} catch (e) {
log('[PlaybackSever] Error: $e');
return Response.internalServerError();
}
}
}

48
lib/services/server/server.dart Executable file
View File

@@ -0,0 +1,48 @@
import 'dart:developer';
import 'dart:io';
import 'dart:math' hide log;
import 'package:flutter/foundation.dart';
import 'package:get/get.dart' hide Response;
import 'package:rhythm_box/services/rhythm_media.dart';
import 'package:rhythm_box/services/server/routes/playback.dart';
import 'package:shelf/shelf.dart';
import 'package:shelf/shelf_io.dart';
import 'package:shelf_router/shelf_router.dart';
class PlaybackServerProvider extends GetxController {
HttpServer? _server;
Router? _router;
@override
void onInit() {
_initServer();
super.onInit();
}
Future<void> _initServer() async {
const pipeline = Pipeline();
if (kDebugMode) {
pipeline.addMiddleware(logRequests());
}
final port = Random().nextInt(17500) + 5000;
RhythmMedia.serverPort = port;
_router = Router();
_router!.get("/ping", (Request request) => Response.ok("pong"));
_router!.get(
"/stream/<trackId>",
Get.find<ServerPlaybackRoutesProvider>().getStreamTrackId,
);
_server = await serve(
pipeline.addHandler(_router!.call),
InternetAddress.anyIPv4,
port,
);
log('[Playback] Playback server at http://${_server!.address.host}:${_server!.port}');
}
}

View File

@@ -0,0 +1,17 @@
import 'package:get/get.dart';
import 'package:rhythm_box/services/audio_player/audio_player.dart';
import 'package:rhythm_box/services/local_track.dart';
import 'package:rhythm_box/services/sourced_track/sourced_track.dart';
class SourcedTrackProvider extends GetxController {
Future<SourcedTrack?> fetch(RhythmMedia? media) async {
final track = media?.track;
if (track == null || track is LocalTrack) {
return null;
}
final sourcedTrack = await SourcedTrack.fetchFromTrack(track: track);
return sourcedTrack;
}
}

View File

@@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:rhythm_box/providers/audio_player.dart';
import 'package:rhythm_box/providers/spotify.dart';
import 'package:rhythm_box/widgets/auto_cache_image.dart';
import 'package:skeletonizer/skeletonizer.dart';
@@ -65,6 +66,10 @@ class _PlaylistTrackListState extends State<PlaylistTrackList> {
item?.artists!.map((x) => x.name!).join(', ') ??
'Please stand by...',
),
onTap: () {
if (item == null) return;
Get.find<AudioPlayerProvider>().load([item], autoPlay: true);
},
);
},
),