Big improvements, usable and pretty app
This commit is contained in:
parent
1b288c2655
commit
9fefb780d1
|
@ -0,0 +1,4 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
late String token;
|
||||
TextStyle globalTextStyle = TextStyle(color: Colors.grey[350]);
|
316
lib/main.dart
316
lib/main.dart
|
@ -1,22 +1,13 @@
|
|||
// ignore_for_file: prefer_const_literals_to_create_immutables, avoid_print
|
||||
|
||||
import 'dart:convert';
|
||||
import 'package:fip_parser_ui/queries.dart';
|
||||
import 'package:fip_parser_ui/globals.dart';
|
||||
import 'package:fip_parser_ui/providers/home.dart';
|
||||
import 'package:fip_parser_ui/providers/player.dart';
|
||||
import 'package:fip_parser_ui/screens/home.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:graphql/client.dart';
|
||||
import 'package:retry/retry.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:youtube_explode_dart/youtube_explode_dart.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:kplayer/kplayer.dart';
|
||||
|
||||
const int queryTimeout = 8;
|
||||
late String token;
|
||||
const hours = 3;
|
||||
const String radio = 'groove';
|
||||
PlayerController? player;
|
||||
|
||||
Future<void> main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
Player.boot();
|
||||
|
@ -30,294 +21,17 @@ class FipyApp extends StatelessWidget {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// final radioList = [
|
||||
// 'fip',
|
||||
// 'electro',
|
||||
// 'groove',
|
||||
// 'rock',
|
||||
// 'jazz',
|
||||
// 'pop',
|
||||
// 'reggae',
|
||||
// 'world',
|
||||
// 'nouveautes',
|
||||
// ];
|
||||
|
||||
// #######
|
||||
|
||||
return MaterialApp(
|
||||
title: 'Fipy',
|
||||
theme: ThemeData(
|
||||
primarySwatch: Colors.blue, backgroundColor: Colors.grey[900]),
|
||||
home: const HomePage(title: 'Fip moi ça'),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class HomePage extends StatefulWidget {
|
||||
const HomePage({Key? key, required this.title}) : super(key: key);
|
||||
|
||||
final String title;
|
||||
|
||||
@override
|
||||
State<HomePage> createState() => _HomePageState();
|
||||
}
|
||||
|
||||
class _HomePageState extends State<HomePage> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: SingleChildScrollView(
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
Container(
|
||||
color: Colors.grey[900],
|
||||
height: 50,
|
||||
child: Center(
|
||||
child: Text(
|
||||
'Radio: ' + radio + '\nHistorique: ' + hours.toString() + 'h',
|
||||
style: globalTextStyle,
|
||||
),
|
||||
),
|
||||
),
|
||||
FutureBuilder<List<Track>>(
|
||||
future: getTracks(),
|
||||
builder: (
|
||||
BuildContext context,
|
||||
AsyncSnapshot<List<Track>> snapshot,
|
||||
) {
|
||||
print(snapshot.connectionState);
|
||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||
return const CircularProgressIndicator();
|
||||
} else if (snapshot.connectionState == ConnectionState.done) {
|
||||
if (snapshot.hasError) {
|
||||
return const Text('Error');
|
||||
} else if (snapshot.hasData) {
|
||||
return Table(
|
||||
defaultVerticalAlignment:
|
||||
TableCellVerticalAlignment.middle,
|
||||
children: snapshot.data!
|
||||
.map((item) => _buildTableRow(item))
|
||||
.toList()
|
||||
..insert(
|
||||
0,
|
||||
_buildTableRow(Track(
|
||||
number: -1,
|
||||
title: 'Titre',
|
||||
artiste: 'Artiste',
|
||||
album: 'Album',
|
||||
id: 'URL')),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return const Text('Empty data');
|
||||
}
|
||||
} else {
|
||||
return Text('State: ${snapshot.connectionState}');
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
return MultiProvider(
|
||||
providers: [
|
||||
ChangeNotifierProvider(create: (_) => PlayerProvider()),
|
||||
ChangeNotifierProvider(create: (_) => HomeProvider()),
|
||||
],
|
||||
child: MaterialApp(
|
||||
title: 'Fipy',
|
||||
theme: ThemeData(
|
||||
primarySwatch: Colors.blue, backgroundColor: Colors.grey[900]),
|
||||
home: const HomeScreen(title: 'Fipy'),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Widget trackLine(Track track) {
|
||||
return SizedBox(
|
||||
height: 30,
|
||||
child: Row(
|
||||
children: [Text(track.title + ' - ' + track.artiste)],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
class Track {
|
||||
final int number;
|
||||
final String title;
|
||||
final String artiste;
|
||||
final String? album;
|
||||
String? id;
|
||||
|
||||
Track(
|
||||
{required this.number,
|
||||
required this.title,
|
||||
required this.artiste,
|
||||
this.album,
|
||||
this.id});
|
||||
}
|
||||
|
||||
GraphQLClient initClient() {
|
||||
final _httpLink = HttpLink(
|
||||
'https://openapi.radiofrance.fr/v1/graphql?x-token=$token',
|
||||
);
|
||||
|
||||
final GraphQLClient client = GraphQLClient(
|
||||
cache: GraphQLCache(),
|
||||
link: _httpLink,
|
||||
);
|
||||
|
||||
return client;
|
||||
}
|
||||
|
||||
Future<List<Track>> getTracks() async {
|
||||
final client = initClient();
|
||||
|
||||
final now = (DateTime.now().millisecondsSinceEpoch ~/ 1000);
|
||||
final yesterday = now - (hours * 3600);
|
||||
final String radioQuery;
|
||||
if (radio == 'fip') {
|
||||
radioQuery = 'FIP';
|
||||
} else {
|
||||
radioQuery = 'FIP_${radio.toUpperCase()}';
|
||||
}
|
||||
|
||||
final QueryOptions options = QueryOptions(
|
||||
document: gql(getSongs),
|
||||
pollInterval: const Duration(milliseconds: 50),
|
||||
variables: <String, dynamic>{
|
||||
'start': yesterday,
|
||||
'end': now,
|
||||
'radio': radioQuery,
|
||||
},
|
||||
);
|
||||
|
||||
QueryResult result;
|
||||
result = await retry(
|
||||
() async => await client
|
||||
.query(options)
|
||||
.timeout(const Duration(seconds: queryTimeout)),
|
||||
onRetry: (_) => print('Timeout, retry...'),
|
||||
);
|
||||
|
||||
if (result.hasException) {
|
||||
print(result.exception.toString());
|
||||
}
|
||||
|
||||
final List data = result.data?['grid'] ?? [];
|
||||
print(data.length.toString() + ' songs');
|
||||
|
||||
// #######
|
||||
|
||||
List<Track> songsList = [];
|
||||
int trackNbr = 0;
|
||||
|
||||
for (Map track in data) {
|
||||
track = track['track'];
|
||||
trackNbr++;
|
||||
final String title = track['title'];
|
||||
final String artiste;
|
||||
if (track['mainArtists'].isNotEmpty) {
|
||||
artiste = track['mainArtists'].first;
|
||||
} else {
|
||||
artiste = '';
|
||||
}
|
||||
final String album = track['albumTitle'];
|
||||
|
||||
final thisSong =
|
||||
Track(number: trackNbr, title: title, artiste: artiste, album: album);
|
||||
songsList.add(thisSong);
|
||||
|
||||
// resultUrl = yt.search.search(title + ' ' + artiste);
|
||||
|
||||
// resultUrlPool.add(resultUrl);
|
||||
|
||||
// resultUrl.then((value) {
|
||||
// try {
|
||||
// thisSong.url = value.first.url;
|
||||
// } catch (e) {
|
||||
// thisSong.url = '';
|
||||
// }
|
||||
// songsList.add(thisSong);
|
||||
// });
|
||||
}
|
||||
|
||||
// for (var searchTrack in resultUrlPool) {
|
||||
// await searchTrack;
|
||||
// }
|
||||
|
||||
songsList.sort((a, b) => a.number.compareTo(b.number));
|
||||
|
||||
return songsList;
|
||||
}
|
||||
|
||||
TableRow _buildTableRow(Track track) {
|
||||
final textStyle = TextStyle(
|
||||
fontWeight: track.number == -1 ? FontWeight.w700 : FontWeight.normal,
|
||||
color: Colors.grey[350]);
|
||||
const rowPadding = EdgeInsets.all(10);
|
||||
var yt = YoutubeExplode();
|
||||
|
||||
return TableRow(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey[850],
|
||||
),
|
||||
children: [
|
||||
TableCell(
|
||||
child: Padding(
|
||||
padding: rowPadding,
|
||||
child: InkWell(
|
||||
onTap: () async {
|
||||
if (track.id == null) {
|
||||
final resultUrl = await yt.search
|
||||
.search(track.title + ' ' + track.artiste);
|
||||
track.id = resultUrl.first.id.value;
|
||||
}
|
||||
if (track.id != null) {
|
||||
// Map<String, String> header = {
|
||||
// 'Access-Control-Allow-Origin': '*',
|
||||
// };
|
||||
final response = await http.get(
|
||||
Uri.parse('https://ytdl.p2p.legal/ytdl.sh?${track.id}'),
|
||||
);
|
||||
final dlLink = response.body.replaceFirst('/dl/', '/play/');
|
||||
print(dlLink);
|
||||
player?.dispose();
|
||||
Future.delayed(const Duration(milliseconds: 5));
|
||||
player = Player.network(dlLink, autoPlay: true);
|
||||
// player = Player.asset("assets/uka.mp3");
|
||||
player!.play();
|
||||
}
|
||||
},
|
||||
child: Text(track.title, style: textStyle),
|
||||
)),
|
||||
),
|
||||
TableCell(
|
||||
child: Padding(
|
||||
padding: rowPadding,
|
||||
child: Text(track.artiste, style: textStyle),
|
||||
),
|
||||
),
|
||||
TableCell(
|
||||
child: Padding(
|
||||
padding: rowPadding,
|
||||
child: Text(track.album ?? '', style: textStyle),
|
||||
),
|
||||
),
|
||||
TableCell(
|
||||
child: Padding(
|
||||
padding: rowPadding,
|
||||
child: InkWell(
|
||||
onTap: () async {
|
||||
if (track.id == null) {
|
||||
final resultUrl = await yt.search
|
||||
.search(track.title + ' ' + track.artiste);
|
||||
track.id = resultUrl.first.id.value;
|
||||
}
|
||||
if (track.id != null) {
|
||||
final response = await http.get(Uri.parse(
|
||||
'https://ytdl.p2p.legal/ytdl.sh?${track.id}'));
|
||||
final dlLink = response.body;
|
||||
if (await canLaunchUrl(Uri.parse(dlLink))) {
|
||||
launchUrl(Uri.parse(dlLink));
|
||||
}
|
||||
Clipboard.setData(ClipboardData(text: dlLink));
|
||||
}
|
||||
},
|
||||
child: Text('Télécharger', style: textStyle),
|
||||
)),
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
TextStyle globalTextStyle = TextStyle(color: Colors.grey[350]);
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
class HomeProvider with ChangeNotifier {
|
||||
void reload() {
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
class PlayerProvider with ChangeNotifier {
|
||||
void reload() {
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,530 @@
|
|||
// ignore_for_file: prefer_const_literals_to_create_immutables, avoid_print
|
||||
|
||||
import 'package:fip_parser_ui/globals.dart';
|
||||
import 'package:fip_parser_ui/providers/home.dart';
|
||||
import 'package:fip_parser_ui/providers/player.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:fip_parser_ui/queries.dart';
|
||||
import 'package:graphql/client.dart';
|
||||
import 'package:kplayer/kplayer.dart';
|
||||
import 'package:miniplayer/miniplayer.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:retry/retry.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:youtube_explode_dart/youtube_explode_dart.dart';
|
||||
|
||||
PlayerController? player;
|
||||
Track? currentTrack;
|
||||
List<Track> trackList = [];
|
||||
String radio = 'groove';
|
||||
|
||||
class HomeScreen extends StatefulWidget {
|
||||
const HomeScreen({Key? key, required this.title}) : super(key: key);
|
||||
final String title;
|
||||
@override
|
||||
State<HomeScreen> createState() => _HomeScreenState();
|
||||
}
|
||||
|
||||
class _HomeScreenState extends State<HomeScreen> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final MiniplayerController controller = MiniplayerController();
|
||||
const hours = 8;
|
||||
|
||||
return Stack(
|
||||
children: <Widget>[
|
||||
Scaffold(
|
||||
body: SingleChildScrollView(
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
Container(
|
||||
color: Colors.grey[900],
|
||||
height: 50,
|
||||
child: Center(
|
||||
child: DropdownButton(
|
||||
dropdownColor: Colors.grey[900],
|
||||
value: radio,
|
||||
style: TextStyle(fontSize: 15, color: Colors.grey[300]),
|
||||
underline: const SizedBox(),
|
||||
iconSize: 22,
|
||||
onChanged: (String? newRadio) {
|
||||
setState(() {
|
||||
radio = newRadio!;
|
||||
getTracks(radio, hours);
|
||||
});
|
||||
},
|
||||
items: radioList),
|
||||
),
|
||||
),
|
||||
FutureBuilder<List<Track>>(
|
||||
future: getTracks(radio, hours),
|
||||
builder: (
|
||||
BuildContext context,
|
||||
AsyncSnapshot<List<Track>> snapshot,
|
||||
) {
|
||||
print(snapshot.connectionState);
|
||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||
return Stack(children: [
|
||||
Container(
|
||||
height: 10000,
|
||||
width: 10000,
|
||||
color: const Color(0xFF121212)),
|
||||
Center(
|
||||
child: Column(children: [
|
||||
const SizedBox(height: 20),
|
||||
SizedBox(
|
||||
height: 30,
|
||||
width: 30,
|
||||
child: CircularProgressIndicator(
|
||||
color: Colors.amber[100],
|
||||
),
|
||||
),
|
||||
]),
|
||||
),
|
||||
]);
|
||||
} else if (snapshot.connectionState ==
|
||||
ConnectionState.done) {
|
||||
if (snapshot.hasError) {
|
||||
return const Text('Error');
|
||||
} else if (snapshot.hasData) {
|
||||
return Table(
|
||||
defaultVerticalAlignment:
|
||||
TableCellVerticalAlignment.middle,
|
||||
children: snapshot.data!
|
||||
.map((item) => _buildTableRow(item, context))
|
||||
.toList()
|
||||
..insert(
|
||||
0,
|
||||
_buildTableRow(
|
||||
Track(
|
||||
number: -1,
|
||||
title: 'TITRE',
|
||||
artiste: 'ARTISTE',
|
||||
album: 'ALBUM',
|
||||
id: 'URL'),
|
||||
context),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return const Text('Empty data');
|
||||
}
|
||||
} else {
|
||||
return Text('State: ${snapshot.connectionState}');
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Consumer<PlayerProvider>(builder: (context, playerProvider, _) {
|
||||
TextEditingController trackTitle = TextEditingController();
|
||||
TextEditingController trackArtiste = TextEditingController();
|
||||
trackTitle.text = currentTrack?.title ?? '';
|
||||
trackArtiste.text = currentTrack?.artiste ?? '';
|
||||
return Miniplayer(
|
||||
controller: controller,
|
||||
backgroundColor: Colors.grey[900]!,
|
||||
minHeight: 70,
|
||||
maxHeight: 370,
|
||||
builder: (height, percentage) {
|
||||
return Row(
|
||||
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
currentTrack?.id != null
|
||||
? Image.network(
|
||||
'https://img.youtube.com/vi/${currentTrack?.id}/1.jpg',
|
||||
width: 93,
|
||||
)
|
||||
: const SizedBox(width: 100),
|
||||
Expanded(
|
||||
child: Column(children: [
|
||||
SizedBox(
|
||||
height: 20,
|
||||
child: TextField(
|
||||
enabled: false,
|
||||
maxLines: 1,
|
||||
controller: trackTitle,
|
||||
decoration: const InputDecoration(
|
||||
border: InputBorder.none,
|
||||
focusedBorder: InputBorder.none,
|
||||
enabledBorder: InputBorder.none,
|
||||
disabledBorder: InputBorder.none,
|
||||
contentPadding: EdgeInsets.only(left: 15),
|
||||
),
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: Colors.grey[300],
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
child: TextField(
|
||||
enabled: false,
|
||||
maxLines: 1,
|
||||
controller: trackArtiste,
|
||||
decoration: const InputDecoration(
|
||||
border: InputBorder.none,
|
||||
focusedBorder: InputBorder.none,
|
||||
enabledBorder: InputBorder.none,
|
||||
disabledBorder: InputBorder.none,
|
||||
contentPadding: EdgeInsets.only(left: 15),
|
||||
),
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: Colors.grey[500],
|
||||
),
|
||||
),
|
||||
),
|
||||
]),
|
||||
),
|
||||
IconButton(
|
||||
icon: Icon(Icons.skip_previous,
|
||||
color: Colors.grey[500], size: 32),
|
||||
onPressed: () {
|
||||
if (currentTrack != null && currentTrack!.number > 1) {
|
||||
currentTrack = trackList.firstWhere((element) =>
|
||||
element.number == currentTrack!.number - 1);
|
||||
playTrack(context, currentTrack!);
|
||||
}
|
||||
}),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
player?.playing ?? false
|
||||
? player?.pause()
|
||||
: player?.play();
|
||||
playerProvider.reload();
|
||||
},
|
||||
child: Icon(
|
||||
player?.playing ?? false
|
||||
? Icons.pause
|
||||
: Icons.play_arrow,
|
||||
color: Colors.grey[900],
|
||||
size: 32),
|
||||
style: ElevatedButton.styleFrom(
|
||||
shape: const CircleBorder(),
|
||||
padding: const EdgeInsets.all(12),
|
||||
primary: Colors.grey[300],
|
||||
onPrimary: Colors.grey[900],
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
icon: Icon(Icons.skip_next,
|
||||
color: Colors.grey[500], size: 32),
|
||||
onPressed: () {
|
||||
if (currentTrack != null &&
|
||||
currentTrack!.number < trackList.last.number) {
|
||||
currentTrack = trackList.firstWhere((element) =>
|
||||
element.number == currentTrack!.number + 1);
|
||||
playTrack(context, currentTrack!);
|
||||
}
|
||||
}),
|
||||
const Spacer(),
|
||||
SliderTheme(
|
||||
data: const SliderThemeData(
|
||||
thumbShape: RoundSliderThumbShape(enabledThumbRadius: 7),
|
||||
overlayShape:
|
||||
RoundSliderThumbShape(enabledThumbRadius: 8),
|
||||
trackHeight: 2.5,
|
||||
),
|
||||
child: SizedBox(
|
||||
width: 130,
|
||||
child: Slider(
|
||||
value: player?.volume ?? 1,
|
||||
max: 1,
|
||||
onChanged: (double value) {
|
||||
if (player?.volume != null) {
|
||||
player!.volume = value;
|
||||
playerProvider.reload();
|
||||
}
|
||||
},
|
||||
activeColor: Colors.grey[400],
|
||||
inactiveColor: Colors.grey[700],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 20)
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
})
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Widget trackLine(Track track) {
|
||||
return SizedBox(
|
||||
height: 30,
|
||||
child: Row(
|
||||
children: [Text(track.title + ' - ' + track.artiste)],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
class Track {
|
||||
final int number;
|
||||
final String title;
|
||||
final String artiste;
|
||||
final String? album;
|
||||
String? id;
|
||||
Duration? duration;
|
||||
|
||||
Track(
|
||||
{required this.number,
|
||||
required this.title,
|
||||
required this.artiste,
|
||||
this.album,
|
||||
this.id,
|
||||
this.duration});
|
||||
}
|
||||
|
||||
GraphQLClient initClient() {
|
||||
final _httpLink = HttpLink(
|
||||
'https://openapi.radiofrance.fr/v1/graphql?x-token=$token',
|
||||
);
|
||||
|
||||
final GraphQLClient client = GraphQLClient(
|
||||
cache: GraphQLCache(),
|
||||
link: _httpLink,
|
||||
);
|
||||
|
||||
return client;
|
||||
}
|
||||
|
||||
Future<List<Track>> getTracks(String radio, int hours) async {
|
||||
const int queryTimeout = 8;
|
||||
|
||||
final client = initClient();
|
||||
|
||||
final now = (DateTime.now().millisecondsSinceEpoch ~/ 1000);
|
||||
final yesterday = now - (hours * 3600);
|
||||
final String radioQuery;
|
||||
if (radio == 'fip') {
|
||||
radioQuery = 'FIP';
|
||||
} else {
|
||||
radioQuery = 'FIP_${radio.toUpperCase()}';
|
||||
}
|
||||
|
||||
final QueryOptions options = QueryOptions(
|
||||
document: gql(getSongs),
|
||||
pollInterval: const Duration(milliseconds: 50),
|
||||
variables: <String, dynamic>{
|
||||
'start': yesterday,
|
||||
'end': now,
|
||||
'radio': radioQuery,
|
||||
},
|
||||
);
|
||||
|
||||
QueryResult result;
|
||||
result = await retry(
|
||||
() async => await client
|
||||
.query(options)
|
||||
.timeout(const Duration(seconds: queryTimeout)),
|
||||
onRetry: (_) => print('Timeout, retry...'),
|
||||
);
|
||||
|
||||
if (result.hasException) {
|
||||
print(result.exception.toString());
|
||||
}
|
||||
|
||||
final List data = result.data?['grid'] ?? [];
|
||||
print(data.length.toString() + ' songs');
|
||||
|
||||
// #######
|
||||
|
||||
int trackNbr = 0;
|
||||
|
||||
trackList.clear();
|
||||
final yt = YoutubeExplode();
|
||||
List resultUrlPool = [];
|
||||
|
||||
for (Map track in data) {
|
||||
track = track['track'];
|
||||
trackNbr++;
|
||||
final String title = track['title'];
|
||||
final String artiste;
|
||||
if (track['mainArtists'].isNotEmpty) {
|
||||
artiste = track['mainArtists'].first;
|
||||
} else {
|
||||
artiste = '';
|
||||
}
|
||||
final String album = track['albumTitle'];
|
||||
|
||||
final thisTrack =
|
||||
Track(number: trackNbr, title: title, artiste: artiste, album: album);
|
||||
trackList.add(thisTrack);
|
||||
|
||||
final secondMatch = artiste == '' ? album : artiste;
|
||||
final resultUrl = yt.search.search(title + ' ' + secondMatch);
|
||||
|
||||
resultUrlPool.add(resultUrl);
|
||||
|
||||
resultUrl.then((value) {
|
||||
try {
|
||||
trackList[trackNbr - 1].id = value.first.id.value;
|
||||
} catch (e) {
|
||||
print(
|
||||
'Error: ' + trackList[trackNbr - 1].title + ' -> ' + e.toString());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
trackList.sort((a, b) => a.number.compareTo(b.number));
|
||||
|
||||
final secondMatch =
|
||||
trackList[0].artiste == '' ? trackList[0].album : trackList[0].artiste;
|
||||
yt.search.search(trackList[0].title + ' ' + secondMatch!).then((resultUrl) {
|
||||
trackList[0].id = resultUrl.first.id.value;
|
||||
|
||||
player = Player.network(
|
||||
"https://invidious.fdn.fr/embed/${trackList[0].id}?raw=1&?listen=1");
|
||||
currentTrack = trackList[0];
|
||||
});
|
||||
return trackList;
|
||||
}
|
||||
|
||||
TableRow _buildTableRow(Track track, BuildContext context) {
|
||||
final textStyle = TextStyle(
|
||||
fontWeight: track.number == -1 ? FontWeight.w200 : FontWeight.normal,
|
||||
color: Colors.grey[500],
|
||||
fontSize: track.number == -1 ? 15 : 14);
|
||||
const rowPadding = EdgeInsets.all(10);
|
||||
final yt = YoutubeExplode();
|
||||
|
||||
return TableRow(
|
||||
decoration: const BoxDecoration(
|
||||
color: Color(0xFF121212),
|
||||
),
|
||||
children: [
|
||||
TableCell(
|
||||
child: Padding(
|
||||
padding: rowPadding,
|
||||
child: InkWell(
|
||||
onTap: () async {
|
||||
if (track.number != -1) {
|
||||
playTrack(context, track);
|
||||
}
|
||||
},
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Consumer<PlayerProvider>(
|
||||
builder: (context, playerProvider, _) {
|
||||
return Text(track.title,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.normal,
|
||||
color: currentTrack == track
|
||||
? Colors.green
|
||||
: Colors.grey[350],
|
||||
fontSize: 14));
|
||||
}),
|
||||
const SizedBox(height: 5),
|
||||
if (track.number != -1)
|
||||
Text(track.artiste,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.normal,
|
||||
color: Colors.grey[500],
|
||||
fontSize: 13))
|
||||
]),
|
||||
)),
|
||||
),
|
||||
TableCell(
|
||||
child: Padding(
|
||||
padding: rowPadding,
|
||||
child: Text(track.album ?? '', style: textStyle),
|
||||
),
|
||||
),
|
||||
TableCell(
|
||||
child: Padding(
|
||||
padding: rowPadding,
|
||||
child: InkWell(
|
||||
onTap: () async {
|
||||
if (track.id == null) {
|
||||
final secondMatch =
|
||||
track.artiste == '' ? track.album : track.artiste;
|
||||
final resultUrl = await yt.search
|
||||
.search(track.title + ' ' + secondMatch!);
|
||||
track.id = resultUrl.first.id.value;
|
||||
}
|
||||
if (track.id != null) {
|
||||
final response = await http.get(Uri.parse(
|
||||
'https://ytdl.p2p.legal/ytdl.sh?${track.id}'));
|
||||
final dlLink = response.body;
|
||||
if (await canLaunchUrl(Uri.parse(dlLink))) {
|
||||
launchUrl(Uri.parse(dlLink));
|
||||
}
|
||||
}
|
||||
},
|
||||
child: Text('TÉLÉCHARGER', style: textStyle),
|
||||
)),
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
Future playTrack(BuildContext context, Track track) async {
|
||||
var yt = YoutubeExplode();
|
||||
PlayerProvider playerProvider =
|
||||
Provider.of<PlayerProvider>(context, listen: false);
|
||||
HomeProvider homeProvider = Provider.of<HomeProvider>(context, listen: false);
|
||||
|
||||
track = trackList[track.number - 1];
|
||||
|
||||
if (track.id == null) {
|
||||
final secondMatch = track.artiste == '' ? track.album : track.artiste;
|
||||
final resultUrl = await yt.search.search(track.title + ' ' + secondMatch!);
|
||||
track.id = resultUrl.first.id.value;
|
||||
}
|
||||
player?.dispose();
|
||||
Future.delayed(const Duration(milliseconds: 5));
|
||||
player = Player.network(
|
||||
"https://invidious.fdn.fr/embed/${track.id}?raw=1&?listen=1");
|
||||
print(track.id);
|
||||
|
||||
try {
|
||||
player!.play();
|
||||
} catch (e) {
|
||||
print('Play error: ' + e.toString());
|
||||
}
|
||||
Future.delayed(const Duration(milliseconds: 50));
|
||||
player!.callback = (PlayerEvent event) {
|
||||
if (event.name == 'status') {
|
||||
var nextTrack =
|
||||
trackList.firstWhere((element) => element.number == track.number + 1);
|
||||
playTrack(context, nextTrack);
|
||||
}
|
||||
};
|
||||
currentTrack = track;
|
||||
playerProvider.reload();
|
||||
homeProvider.reload();
|
||||
}
|
||||
|
||||
// final radioList = [
|
||||
// 'fip',
|
||||
// 'electro',
|
||||
// 'groove',
|
||||
// 'rock',
|
||||
// 'jazz',
|
||||
// 'pop',
|
||||
// 'reggae',
|
||||
// 'world',
|
||||
// 'nouveautes',
|
||||
// ];
|
||||
|
||||
List<DropdownMenuItem<String>> get radioList {
|
||||
List<DropdownMenuItem<String>> menuItems = [
|
||||
const DropdownMenuItem(child: Text("FIP"), value: "fip"),
|
||||
const DropdownMenuItem(child: Text("Electro"), value: "electro"),
|
||||
const DropdownMenuItem(child: Text("Groove"), value: "groove"),
|
||||
const DropdownMenuItem(child: Text("Rock"), value: "rock"),
|
||||
const DropdownMenuItem(child: Text("Jazz"), value: "jazz"),
|
||||
const DropdownMenuItem(child: Text("Pop"), value: "pop"),
|
||||
const DropdownMenuItem(child: Text("Reggae"), value: "reggae"),
|
||||
const DropdownMenuItem(child: Text("World"), value: "world"),
|
||||
const DropdownMenuItem(child: Text("Nouveautes"), value: "nouveautes"),
|
||||
];
|
||||
return menuItems;
|
||||
}
|
21
pubspec.lock
21
pubspec.lock
|
@ -317,6 +317,20 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.7.0"
|
||||
miniplayer:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: "/home/poka/dev/flutter/miniplayer"
|
||||
relative: false
|
||||
source: path
|
||||
version: "1.0.1"
|
||||
nested:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: nested
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.0.0"
|
||||
normalize:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -408,6 +422,13 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "4.2.4"
|
||||
provider:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: provider
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "6.0.2"
|
||||
retry:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
|
|
@ -12,13 +12,15 @@ dependencies:
|
|||
sdk: flutter
|
||||
cupertino_icons: ^1.0.2
|
||||
graphql: ^5.0.0
|
||||
# youtube_explode_dart: ^1.10.9+1
|
||||
youtube_explode_dart:
|
||||
youtube_explode_dart: #^1.10.9+1
|
||||
path: /home/poka/dev/flutter/youtube_explode_dart
|
||||
retry: ^3.1.0
|
||||
url_launcher: ^6.1.0
|
||||
http: ^0.13.4
|
||||
kplayer: ^0.1.12
|
||||
miniplayer: # ^1.0.1
|
||||
path: /home/poka/dev/flutter/miniplayer
|
||||
provider: ^6.0.1
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
|
Loading…
Reference in New Issue