// ignore_for_file: prefer_const_literals_to_create_immutables, avoid_print import 'dart:convert'; import 'dart:io'; import 'package:fip_parser_ui/queries.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:flutter/services.dart'; import 'package:kplayer/kplayer.dart'; const int queryTimeout = 8; late String token; const hours = 3; const String radio = 'groove'; PlayerController? player; void main() { WidgetsFlutterBinding.ensureInitialized(); HttpOverrides.global = MyHttpOverrides(); Player.boot(); runApp(const FipyApp()); } class FipyApp extends StatelessWidget { const FipyApp({Key? key}) : super(key: key); @override Widget build(BuildContext context) { if (!File('api-token.json').existsSync()) { print( 'Please copy "api-token.json.template" to "api-token.json" and set your custom API token.\nMore information here: https://developers.radiofrance.fr/projects/new'); exit(1); } final tokenFile = File('api-token.json').readAsStringSync(); final jsonData = json.decode(tokenFile); token = jsonData['token']; // 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 createState() => _HomePageState(); } class _HomePageState extends State { @override Widget build(BuildContext context) { return Scaffold( body: SingleChildScrollView( child: Column( children: [ Container( color: Colors.grey[900], height: 50, child: Center( child: Text( 'Radio: ' + radio + '\nHistorique: ' + hours.toString() + 'h', style: globalTextStyle, ), ), ), FutureBuilder>( future: getTracks(), builder: ( BuildContext context, AsyncSnapshot> 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}'); } }, ), ], ), ), ); } } 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> 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: { '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()); exit(1); } final List data = result.data?['grid'] ?? []; print(data.length.toString() + ' songs'); // ####### List 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) { 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), )), ), ]); } class MyHttpOverrides extends HttpOverrides { @override HttpClient createHttpClient(SecurityContext? context) { return super.createHttpClient(context) ..badCertificateCallback = (X509Certificate cert, String host, int port) => true; } } TextStyle globalTextStyle = TextStyle(color: Colors.grey[350]);