diff --git a/lib/components/playlist_container.dart b/lib/components/playlist_container.dart index 4d1f6e9..9fdb8aa 100644 --- a/lib/components/playlist_container.dart +++ b/lib/components/playlist_container.dart @@ -2,7 +2,7 @@ import 'dart:convert'; import 'package:flutter/gestures.dart'; import 'package:http/http.dart' as http; import 'package:flutter/material.dart'; -import 'package:musico/components/song_card.dart'; +import 'package:musico/widgets/song_card.dart'; import 'package:musico/shimmers/playlistcat_shimmer.dart'; import 'package:musico/models/playlist_category.dart'; diff --git a/lib/constants/api_url.dart b/lib/constants/api_url.dart new file mode 100644 index 0000000..a4f854e --- /dev/null +++ b/lib/constants/api_url.dart @@ -0,0 +1,6 @@ +const baseUrl = "https://music.youtube.com/youtubei/v1"; +const searchUrl = "$baseUrl/search"; +const searchSuggestionUrl = "$baseUrl/music/get_search_suggestions"; +const playerUrl = "$baseUrl/player"; +const nextUrl = "$baseUrl/next"; +const browseUrl = "$baseUrl/browse"; diff --git a/lib/main.dart b/lib/main.dart index c41f99f..1210b14 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -5,12 +5,13 @@ import 'package:musico/screens/base_screen.dart'; import 'package:musico/screens/wrapper.dart'; import 'package:musico/services/Providers/music_player_provider.dart'; import 'package:provider/provider.dart'; -import 'package:shared_preferences/shared_preferences.dart'; import 'package:upgrader/upgrader.dart'; +import 'package:hive_flutter/hive_flutter.dart'; Future main() async { WidgetsFlutterBinding.ensureInitialized(); - SharedPreferences pref = await SharedPreferences.getInstance(); + await Hive.initFlutter(); + Box avatarBox = await Hive.openBox('avatarBox'); await JustAudioBackground.init( androidNotificationChannelId: 'com.ryanheise.bg_demo.channel.audio', androidNotificationChannelName: 'Audio playback', @@ -18,20 +19,25 @@ Future main() async { androidStopForegroundOnPause: true, ); runApp(MyApp( - prefs: pref, + avatarBox: avatarBox, )); } class MyApp extends StatefulWidget { - final SharedPreferences prefs; + final Box avatarBox; - const MyApp({super.key, required this.prefs}); + const MyApp({super.key, required this.avatarBox}); @override State createState() => _MyAppState(); } class _MyAppState extends State { + @override + void initState() { + super.initState(); + } + @override Widget build(BuildContext context) { return MultiProvider( @@ -54,8 +60,8 @@ class _MyAppState extends State { ), home: UpgradeAlert( upgrader: Upgrader(), - child: widget.prefs.getString('username') == null && - widget.prefs.getString('avatar') == null + child: widget.avatarBox.get('username') == null && + widget.avatarBox.get('avatar') == null ? const Wrapper() : const BaseScreen(), ), diff --git a/lib/screens/base_screen.dart b/lib/screens/base_screen.dart index f31b443..f31adba 100644 --- a/lib/screens/base_screen.dart +++ b/lib/screens/base_screen.dart @@ -1,11 +1,14 @@ import 'dart:ui'; import 'package:flutter/material.dart'; +import 'package:flutter_animate/flutter_animate.dart'; import 'package:musico/colors/color.dart'; import 'package:musico/screens/about_screen.dart'; import 'package:musico/screens/home_screen.dart'; import 'package:musico/screens/player_screen.dart'; import 'package:musico/screens/search_screen.dart'; import 'package:musico/services/Providers/music_player_provider.dart'; +import 'package:musico/services/api/player_api.dart'; +import 'package:musico/widgets/floating_mediabar.dart'; import 'package:page_transition/page_transition.dart'; import 'package:provider/provider.dart'; import 'package:google_fonts/google_fonts.dart'; @@ -19,7 +22,11 @@ class BaseScreen extends StatefulWidget { class _BaseScreenState extends State { var currentIndex = 0; - List widgets = [const HomeScreen(),const SearchScreen(),const AboutScreen()]; + List widgets = [ + const HomeScreen(), + const SearchScreen(), + const AboutScreen() + ]; Widget mainBody() { switch (currentIndex) { @@ -43,115 +50,9 @@ class _BaseScreenState extends State { mainBody(), Consumer( builder: (_, musicPlayerProvider, child) { - return Positioned( + return const Positioned( bottom: 75, - child: Visibility( - visible: musicPlayerProvider.audio == null || - musicPlayerProvider.songs == [] - ? false - : true, - child: Container( - alignment: Alignment.center, - margin: const EdgeInsets.symmetric(horizontal: 10.0), - decoration: BoxDecoration( - color: primaryThemeColor, - borderRadius: BorderRadius.circular(10.0)), - width: MediaQuery.of(context).size.width * 0.95, - child: GestureDetector( - onVerticalDragEnd: (details) async { - await musicPlayerProvider.advancedPlayer.stop(); - musicPlayerProvider.audio = null; - musicPlayerProvider.isNewSongSet = true; - }, - child: ClipRRect( - borderRadius: BorderRadius.circular(12.0), - child: ListTile( - title: Text( - musicPlayerProvider.songs.isNotEmpty - ? musicPlayerProvider - .songs[musicPlayerProvider.currentIndex] - .title - : "", - maxLines: 1, - style: GoogleFonts.poppins( - color: Colors.black, - fontWeight: FontWeight.w600), - overflow: TextOverflow.ellipsis, - ), - subtitle: Text( - musicPlayerProvider.songs.isNotEmpty - ? musicPlayerProvider - .songs[musicPlayerProvider.currentIndex] - .author - : "", - maxLines: 1, - style: GoogleFonts.poppins( - color: Colors.black, - fontWeight: FontWeight.w500), - overflow: TextOverflow.ellipsis, - ), - leading: ClipRRect( - borderRadius: BorderRadius.circular(5.0), - child: Image( - image: NetworkImage(musicPlayerProvider - .songs.isNotEmpty - ? musicPlayerProvider - .songs[musicPlayerProvider.currentIndex] - .thumbnail - : ""), - ), - ), - trailing: Row( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.end, - children: [ - IconButton( - iconSize: 35, - icon: musicPlayerProvider.isPlaying - ? const Icon( - Icons.pause, - size: 35, - color: Colors.black, - ) - : const Icon( - Icons.play_arrow, - size: 35, - color: Colors.black, - ), - onPressed: () { - musicPlayerProvider.togglePlayback(); - }, - ), - Visibility( - visible: - musicPlayerProvider.advancedPlayer.hasNext - ? true - : false, - child: IconButton( - iconSize: 35, - icon: const Icon( - Icons.skip_next, - color: Colors.black, - ), - onPressed: () { - musicPlayerProvider.playNextSong(); - }, - ), - ), - ], - ), - ), - ), - onTap: () { - Navigator.push( - context, - PageTransition( - child: const PlayerScreen(), - type: PageTransitionType.bottomToTop)); - }, - ), - ), - )); + child: FloatingMediaBar()); }) ]), backgroundColor: Colors.transparent, diff --git a/lib/screens/home_screen.dart b/lib/screens/home_screen.dart index c280529..a33692b 100644 --- a/lib/screens/home_screen.dart +++ b/lib/screens/home_screen.dart @@ -1,11 +1,11 @@ //import 'package:curved_navigation_bar/curved_navigation_bar.dart'; import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; +import 'package:hive/hive.dart'; import 'package:musico/colors/color.dart'; import 'package:musico/components/playlist_container.dart'; import 'package:musico/screens/wrapper.dart'; import 'package:page_transition/page_transition.dart'; -import 'package:shared_preferences/shared_preferences.dart'; class HomeScreen extends StatefulWidget { const HomeScreen({super.key}); @@ -16,15 +16,15 @@ class HomeScreen extends StatefulWidget { class _HomeScreenState extends State { var settext = ""; - late final SharedPreferences pref; + late final Box avatarBox; String username = ""; String? avatar; void getsharedpref() async { - pref = await SharedPreferences.getInstance(); + avatarBox = await Hive.openBox('avatarBox'); setState(() { - username = pref.getString('username') ?? ""; - avatar = pref.getString('avatar'); + username = avatarBox.get('username') ?? ""; + avatar = avatarBox.get('avatar'); }); } diff --git a/lib/screens/search_results.dart b/lib/screens/search_results.dart index dd161c4..b6b2fb3 100644 --- a/lib/screens/search_results.dart +++ b/lib/screens/search_results.dart @@ -5,6 +5,7 @@ import 'package:musico/services/Providers/music_player_provider.dart'; import 'package:musico/services/api/search_song_api.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:musico/screens/player_screen.dart'; +import 'package:musico/widgets/floating_mediabar.dart'; import 'package:page_transition/page_transition.dart'; import 'package:musico/shimmers/searchresult_shimmer.dart'; import 'package:provider/provider.dart'; @@ -25,7 +26,6 @@ class _SearchResultScreenState extends State { @override void initState() { - // TODO: implement initState super.initState(); getSongs = SearchSongs().getSongs(widget.message); getVideos = SearchSongs().getVideos(widget.message); @@ -210,130 +210,8 @@ class _SearchResultScreenState extends State { ), Consumer( builder: (_, musicPlayerProvider, child) { - return Positioned( - bottom: 15, - child: Visibility( - visible: musicPlayerProvider.audio == null - ? false - : true, - child: Container( - alignment: Alignment.center, - margin: - const EdgeInsets.symmetric(horizontal: 10.0), - decoration: BoxDecoration( - color: primaryThemeColor, - borderRadius: BorderRadius.circular(10.0)), - width: MediaQuery.of(context).size.width * 0.95, - child: GestureDetector( - onVerticalDragEnd: (details) { - musicPlayerProvider.advancedPlayer.stop(); - musicPlayerProvider.audio = null; - musicPlayerProvider.isNewSongSet = true; - }, - child: ClipRRect( - borderRadius: BorderRadius.circular(12.0), - child: Column( - children: [ - ListTile( - title: Text( - musicPlayerProvider.songs.isNotEmpty - ? musicPlayerProvider - .songs[musicPlayerProvider - .currentIndex] - .title - : "", - maxLines: 1, - style: GoogleFonts.poppins( - color: Colors.black, - fontWeight: FontWeight.w600), - overflow: TextOverflow.ellipsis, - ), - subtitle: Text( - musicPlayerProvider.songs.isNotEmpty - ? musicPlayerProvider - .songs[musicPlayerProvider - .currentIndex] - .author - : "", - maxLines: 1, - style: GoogleFonts.poppins( - color: Colors.black, - fontWeight: FontWeight.w500), - overflow: TextOverflow.ellipsis, - ), - leading: ClipRRect( - borderRadius: - BorderRadius.circular(5.0), - child: Image( - image: NetworkImage( - musicPlayerProvider - .songs.isNotEmpty - ? musicPlayerProvider - .songs[ - musicPlayerProvider - .currentIndex] - .thumbnail - : ""), - ), - ), - trailing: Row( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: - MainAxisAlignment.end, - children: [ - IconButton( - iconSize: 35, - icon: - musicPlayerProvider.isPlaying - ? const Icon( - Icons.pause, - size: 35, - color: Colors.black, - ) - : const Icon( - Icons.play_arrow, - size: 35, - color: Colors.black, - ), - onPressed: () { - musicPlayerProvider - .togglePlayback(); - }, - ), - Visibility( - visible: musicPlayerProvider - .advancedPlayer.hasNext - ? true - : false, - child: IconButton( - iconSize: 35, - icon: const Icon( - Icons.skip_next, - color: Colors.black, - ), - onPressed: () { - musicPlayerProvider - .playNextSong(); - }, - ), - ), - ], - ), - ), - ], - ), - ), - onTap: () { - Navigator.push( - context, - PageTransition( - child: const PlayerScreen(), - type: - PageTransitionType.bottomToTop)); - }, - ), - ), - )); + return const Positioned( + bottom: 15, child: FloatingMediaBar()); }) ], )), diff --git a/lib/screens/search_screen.dart b/lib/screens/search_screen.dart index a2d5c67..d440f6c 100644 --- a/lib/screens/search_screen.dart +++ b/lib/screens/search_screen.dart @@ -1,7 +1,6 @@ import 'dart:async'; import 'package:avatar_glow/avatar_glow.dart'; import 'package:bootstrap_icons/bootstrap_icons.dart'; -import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:flutter_typeahead/flutter_typeahead.dart'; @@ -9,8 +8,8 @@ import 'package:musico/colors/color.dart'; import 'package:musico/screens/search_results.dart'; import 'package:musico/services/api/search_suggestions_api.dart'; import 'package:page_transition/page_transition.dart'; -import 'package:shared_preferences/shared_preferences.dart'; import 'package:speech_to_text/speech_to_text.dart' as stt; +import 'package:hive/hive.dart'; class SearchScreen extends StatefulWidget { const SearchScreen({super.key}); @@ -25,42 +24,42 @@ class _SearchScreenState extends State { bool available = false; bool _isListening = false; stt.SpeechToText speech = stt.SpeechToText(); - late final SharedPreferences prefs; late final SuggestionsController suggestionsController; + late final Box historyBox; bool isHistory = false; @override void initState() { super.initState(); - getsharedpref(); + initializeHive(); suggestionsController = SuggestionsController(); } - void getsharedpref() async { - prefs = await SharedPreferences.getInstance(); + Future initializeHive() async { + historyBox = await Hive.openBox('searchHistory'); } Future sethistory(String historyItem) async { - List history = prefs.getStringList('history') ?? []; + List history = historyBox.get('history') ?? []; if (!history.contains(historyItem)) { history.add(historyItem); - await prefs.setStringList('history', history); + await historyBox.put('history', history); } else { history.remove(historyItem); history.add(historyItem); - await prefs.setStringList('history', history); + await historyBox.put('history', history); } } Future> gethistory() async { - List history = prefs.getStringList('history') ?? []; + List history = historyBox.get('history') ?? []; return history.reversed.toList(); } Future deleteHistoryItem(String historyItem) async { - List history = prefs.getStringList('history') ?? []; + List history = historyBox.get('history') ?? []; history.remove(historyItem); - await prefs.setStringList('history', history); + await historyBox.put('history', history); } Future listenSpeech(setState) async { diff --git a/lib/screens/wrapper.dart b/lib/screens/wrapper.dart index 7f983c8..3689f46 100644 --- a/lib/screens/wrapper.dart +++ b/lib/screens/wrapper.dart @@ -1,8 +1,8 @@ import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; +import 'package:hive/hive.dart'; import 'package:musico/colors/color.dart'; import 'package:musico/screens/base_screen.dart'; -import 'package:shared_preferences/shared_preferences.dart'; class Wrapper extends StatefulWidget { const Wrapper({super.key}); @@ -29,21 +29,22 @@ class _WrapperState extends State { ]; int currentAvatar = 0; String username = ""; - late final SharedPreferences prefs; + late final Box avatarBox; + @override void initState() { super.initState(); - getsharedpref(); + initializaHive(); } - void getsharedpref() async { - prefs = await SharedPreferences.getInstance(); + void initializaHive() async { + avatarBox = await Hive.openBox('avatarBox'); } - void setsharedpref() async { - await prefs.setString('username', username); - await prefs.setString('avatar', avatars[currentAvatar]); + void setAvatar() async { + await avatarBox.put('username', username); + await avatarBox.put('avatar', avatars[currentAvatar]); } @override @@ -167,7 +168,7 @@ class _WrapperState extends State { ElevatedButton( onPressed: () async { if (username != "") { - setsharedpref(); + setAvatar(); Navigator.pushReplacement( context, MaterialPageRoute( diff --git a/lib/services/api/lyrics_api.dart b/lib/services/api/lyrics_api.dart index 3cbbe94..079eca2 100644 --- a/lib/services/api/lyrics_api.dart +++ b/lib/services/api/lyrics_api.dart @@ -1,43 +1,40 @@ import 'dart:convert'; import 'package:dio/dio.dart'; +import 'package:musico/constants/api_url.dart'; +import 'package:musico/utils/client.dart'; final dio = Dio(); class Lyrics { - final String _data = jsonEncode({ - "context": { - "client": { - "hl": "en", - "gl": "US", - "clientName": "ANDROID_MUSIC", - "clientVersion": "5.26.1", - "clientScreen": "WATCH", - "androidSdkVersion": 31 - }, - "thirdParty": {"embedUrl": "https://www.youtube.com/"} - }, - "playbackContext": { - "contentPlaybackContext": {"signatureTimestamp": 19250} - }, - "racyCheckOk": true, - "contentCheckOk": true - }); + ApiClient client = ApiClient(); Future getLyrics(String vid) async { - var response = await dio.post( - 'https://music.youtube.com/youtubei/v1/next?key=AIzaSyC9XL3ZjWddXya6X74dJoCTL-WEYFDNX30&videoId=$vid&fields=contents.singleColumnMusicWatchNextResultsRenderer.tabbedRenderer.watchNextTabbedResultsRenderer.tabs.tabRenderer(endpoint.browseEndpoint.browseId,title)', - data: _data); - var data1 = jsonDecode(response.toString()); - data1 = data1["contents"]["singleColumnMusicWatchNextResultsRenderer"] + var response = await client.post(nextUrl, + queryParamMap: { + "key": "AIzaSyC9XL3ZjWddXya6X74dJoCTL-WEYFDNX30", + "videoId": vid, + "fields": + "contents.singleColumnMusicWatchNextResultsRenderer.tabbedRenderer.watchNextTabbedResultsRenderer.tabs.tabRenderer(endpoint.browseEndpoint.browseId,title)" + }, + ytClientName: "ANDROID_MUSIC"); + + var data = jsonDecode(response.body.toString()); + + data = data["contents"]["singleColumnMusicWatchNextResultsRenderer"] ["tabbedRenderer"]["watchNextTabbedResultsRenderer"]["tabs"][1] ["tabRenderer"]["endpoint"]["browseEndpoint"]["browseId"]; - response = await dio.post( - 'https://music.youtube.com/youtubei/v1/browse?key=AIzaSyC9XL3ZjWddXya6X74dJoCTL-WEYFDNX30&browseId=$data1&fields=contents', - data: _data); - data1 = jsonDecode(response.toString()); + + response = await client.post(browseUrl, + queryParamMap: { + "key": "AIzaSyC9XL3ZjWddXya6X74dJoCTL-WEYFDNX30", + "browseId": data, + "fields": "contents" + }, + ytClientName: "ANDROID_MUSIC"); + data = jsonDecode(response.body.toString()); try { - String lyrics = data1["contents"].isNotEmpty - ? data1["contents"]["sectionListRenderer"]["contents"][0] + String lyrics = data["contents"].isNotEmpty + ? data["contents"]["sectionListRenderer"]["contents"][0] ["musicDescriptionShelfRenderer"]["description"]["runs"][0] ["text"] : "Lyrics not available"; diff --git a/lib/services/api/player_api.dart b/lib/services/api/player_api.dart index fe61030..370ef7e 100644 --- a/lib/services/api/player_api.dart +++ b/lib/services/api/player_api.dart @@ -1,48 +1,55 @@ -import 'dart:convert'; +import 'package:musico/constants/api_url.dart'; import 'package:musico/models/requests/player_req.dart'; import 'package:musico/models/response/player_res.dart'; -import 'package:http/http.dart' as http; +import 'package:musico/utils/client.dart'; Map header = { - "Accept": "*/*", - "Host": "www.youtube.com", - "X-Goog-Api-Key": "AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8", - "Content-Type": "application/json", - "User-Agent": - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36 Edg/105.0.1343.42", - "Origin": "https://www.youtube.com", - "Referer": "https://www.youtube.com/", - "Accept-Encoding": "gzip, deflate", - "Accept-Language": "de,de-DE;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6", - "Access-Control-Allow-Origin": "*" + 'X-Youtube-Client-Name': 'ANDROID_VR', + 'X-Youtube-Client-Version': '1.56.21', + 'X-Goog-Visitor-Id': 'Cgs3UGxBSGJSQjVUayim8ca9BjIKCgJJThIEGgAgJw%3D%3D', + 'Origin': 'https://www.youtube.com', + 'Sec-Fetch-Mode': 'navigate', + 'Content-Type': 'application/json', + 'PREF': 'hl=en' }; -String data = jsonEncode({ +Map data = { "context": { "client": { + "clientName": "ANDROID_VR", + "clientVersion": "1.56.21", + "deviceModel": "Quest 3", + "osVersion": "12", + "osName": "Android", + "androidSdkVersion": 32, "hl": "en", - "gl": "US", - "clientName": "ANDROID_MUSIC", - "clientVersion": "5.26.1", - "clientScreen": "WATCH", - "androidSdkVersion": 31 - }, - "thirdParty": {"embedUrl": "https://www.youtube.com/"} + "timeZone": "UTC", + "utcOffsetMinutes": 0 + } }, "playbackContext": { - "contentPlaybackContext": {"signatureTimestamp": 19250} - }, - "racyCheckOk": true, - "contentCheckOk": true -}); + "contentPlaybackContext": { + "html5Preference": "HTML5_PREF_WANTS", + "signatureTimestamp": 20131 + } + } +}; class Player { + ApiClient client = ApiClient(); + Future getSongDetails(String vid) async { - final response = await http.post( - Uri.parse( - 'https://music.youtube.com/youtubei/v1/player?key=AIzaSyC9XL3ZjWddXya6X74dJoCTL-WEYFDNX30&videoId=$vid&fields=(streamingData.adaptiveFormats(itag,url),videoDetails(author,thumbnail,title,videoId))'), - headers: header, - body: data); + final response = await client.post(playerUrl, + queryParamMap: { + "key": "AIzaSyC9XL3ZjWddXya6X74dJoCTL - WEYFDNX30", + "videoId": vid, + "fields": + "(streamingData.adaptiveFormats(itag,url),videoDetails(author,thumbnail,title,videoId))" + }, + optHeaders: header, + data: data, + ytClientName: 'ANDROID_VR'); + try { SongDetailsReq songinfo = songDetailsReqFromJson(response.body.toString()); diff --git a/lib/services/api/search_song_api.dart b/lib/services/api/search_song_api.dart index ed94381..4e9380e 100644 --- a/lib/services/api/search_song_api.dart +++ b/lib/services/api/search_song_api.dart @@ -1,76 +1,35 @@ import 'dart:convert'; +import 'package:musico/constants/api_url.dart'; import 'package:musico/models/response/search_song.dart'; -import 'package:http/http.dart' as http; - -Map _header = { - "Accept": "*/*", - "Host": "www.youtube.com", - "X-Goog-Api-Key": "AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8", - "Content-Type": "application/json", - "User-Agent": - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36 Edg/105.0.1343.42", - "Origin": "https://www.youtube.com", - "Referer": "https://www.youtube.com/", - "Accept-Encoding": "gzip, deflate", - "Accept-Language": "de,de-DE;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6" -}; - -String _data = jsonEncode({ - "context": { - "client": { - "hl": "en", - "gl": "US", - "clientName": "ANDROID_MUSIC", - "clientVersion": "5.26.1", - "clientScreen": "WATCH", - "androidSdkVersion": 31 - }, - "thirdParty": {"embedUrl": "https://www.youtube.com/"} - }, - "playbackContext": { - "contentPlaybackContext": {"signatureTimestamp": 19250} - }, - "racyCheckOk": true, - "contentCheckOk": true -}); - -String _data1 = jsonEncode({ - "context": { - "client": { - "hl": "en", - "gl": "US", - "clientName": "WEB_REMIX", - "clientVersion": "1.20220918", - "clientScreen": "WATCH", - "androidSdkVersion": 31 - }, - "thirdParty": {"embedUrl": "https://www.youtube.com/"} - }, - "playbackContext": { - "contentPlaybackContext": {"signatureTimestamp": 19250} - }, - "racyCheckOk": true, - "contentCheckOk": true -}); +import 'package:musico/utils/client.dart'; class SearchSongs { + ApiClient client = ApiClient(); + Future> getSongs(String query) async { try { - final response = await http.post( - Uri.parse( - "https://music.youtube.com/youtubei/v1/search?key=AIzaSyC9XL3ZjWddXya6X74dJoCTL-WEYFDNX30&query=$query¶ms=EgWKAQIIAWoMEA4QChADEAQQCRAF&fields=contents.tabbedSearchResultsRenderer.tabs.tabRenderer.content.sectionListRenderer.contents.musicShelfRenderer(contents.musicTwoColumnItemRenderer(thumbnail.musicThumbnailRenderer.thumbnail,title,subtitle,navigationEndpoint.watchEndpoint.videoId),continuations.nextContinuationData.continuation)"), - headers: _header, - body: _data); + final response = await client.post(searchUrl, + queryParamMap: { + "query": query, + "key": "AIzaSyC9XL3ZjWddXya6X74dJoCTL-WEYFDNX30", + "params": "EgWKAQIIAWoMEA4QChADEAQQCRAF", + "fields": + "contents.tabbedSearchResultsRenderer.tabs.tabRenderer.content.sectionListRenderer.contents.musicShelfRenderer(contents.musicTwoColumnItemRenderer(thumbnail.musicThumbnailRenderer.thumbnail,title,subtitle,navigationEndpoint.watchEndpoint.videoId),continuations.nextContinuationData.continuation)" + }, + ytClientName: "ANDROID_MUSIC"); + List li = []; - var data1 = jsonDecode(response.body.toString()); - List content = data1["contents"]["tabbedSearchResultsRenderer"] + var data = jsonDecode(response.body.toString()); + + List content = data["contents"]["tabbedSearchResultsRenderer"] ["tabs"][0]["tabRenderer"]["content"]["sectionListRenderer"] ["contents"]; + late dynamic contents; late String continuation; if (content[0].isNotEmpty) { contents = content[0]["musicShelfRenderer"]["contents"]; - continuation = data1["contents"]["tabbedSearchResultsRenderer"]["tabs"] + continuation = data["contents"]["tabbedSearchResultsRenderer"]["tabs"] [0]["tabRenderer"]["content"]["sectionListRenderer"] ["contents"][0]["musicShelfRenderer"]["continuations"][0] ["nextContinuationData"]["continuation"]; @@ -97,15 +56,23 @@ class SearchSongs { } return li; } catch (e) { + print(e); return []; } } - Future> getVideos(String q) async { - final response = await http.post( - Uri.parse( - 'https://music.youtube.com/youtubei/v1/search?key=AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8 HTTP/2&query=$q¶ms=EgWKAQIQAWoMEA4QChADEAQQCRAF&fields=contents.tabbedSearchResultsRenderer.tabs.tabRenderer.content.sectionListRenderer.contents.musicShelfRenderer(contents.musicResponsiveListItemRenderer(thumbnail.musicThumbnailRenderer.thumbnail,flexColumns.musicResponsiveListItemFlexColumnRenderer.text.runs(text,navigationEndpoint.watchEndpoint.videoId)),continuations)'), - body: _data1); + Future> getVideos(String query) async { + final response = await client.post(searchUrl, + queryParamMap: { + "query": query, + "key": "AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8", + "params": "EgWKAQIQAWoMEA4QChADEAQQCRAF", + "fields": + "contents.tabbedSearchResultsRenderer.tabs.tabRenderer.content.sectionListRenderer.contents.musicShelfRenderer(contents.musicResponsiveListItemRenderer(thumbnail.musicThumbnailRenderer.thumbnail,flexColumns.musicResponsiveListItemFlexColumnRenderer.text.runs(text,navigationEndpoint.watchEndpoint.videoId)),continuations)" + }, + ytClientName: "WEB_REMIX"); + + List li = []; var data1 = jsonDecode(response.body.toString()); List content = data1["contents"]["tabbedSearchResultsRenderer"] @@ -115,31 +82,42 @@ class SearchSongs { late String continuation; if (content[0].isNotEmpty) { contents = content[0]["musicShelfRenderer"]["contents"]; - continuation = content[0]["musicShelfRenderer"]["continuations"][0] - ["nextContinuationData"]["continuation"]; + continuation = + content[0]["musicShelfRenderer"].containsKey('continuations') + ? content[0]["musicShelfRenderer"]["continuations"][0] + ["nextContinuationData"]["continuation"] + : ""; } else if (content.isNotEmpty) { contents = content[1]["musicShelfRenderer"]["contents"]; - continuation = content[1]["musicShelfRenderer"].containsKey('continuations') ? content[1]["musicShelfRenderer"]["continuations"][0]["nextContinuationData"]["continuation"] : ""; + continuation = + content[1]["musicShelfRenderer"].containsKey('continuations') + ? content[1]["musicShelfRenderer"]["continuations"][0] + ["nextContinuationData"]["continuation"] + : ""; } else { return li; } for (int i = 0; i < contents.length; i++) { - if(contents[i]["musicResponsiveListItemRenderer"]["flexColumns"][0]["musicResponsiveListItemFlexColumnRenderer"]["text"] - ["runs"][0]["navigationEndpoint"].containsKey('watchEndpoint')){SongInfo songinfo = SongInfo( - title: contents[i]["musicResponsiveListItemRenderer"]["flexColumns"][0]["musicResponsiveListItemFlexColumnRenderer"] - ["text"]["runs"][0]["text"], - videoId: contents[i]["musicResponsiveListItemRenderer"]["flexColumns"][0]["musicResponsiveListItemFlexColumnRenderer"]["text"] - ["runs"][0]["navigationEndpoint"]["watchEndpoint"]["videoId"], - duration: contents[i]["musicResponsiveListItemRenderer"]["flexColumns"][1]["musicResponsiveListItemFlexColumnRenderer"]["text"]["runs"].length - 1 > 0 - ? contents[i]["musicResponsiveListItemRenderer"]["flexColumns"][1] - ["musicResponsiveListItemFlexColumnRenderer"]["text"]["runs"] - [contents[i]["musicResponsiveListItemRenderer"]["flexColumns"][1]["musicResponsiveListItemFlexColumnRenderer"]["text"]["runs"].length - 1]["text"] - : '0:00', - artists: contents[i]["musicResponsiveListItemRenderer"]["flexColumns"][1]["musicResponsiveListItemFlexColumnRenderer"]["text"]["runs"][0]["text"], - thumbnails: contents[i]["musicResponsiveListItemRenderer"]["thumbnail"]["musicThumbnailRenderer"]["thumbnail"]["thumbnails"][0]["url"], - continuations: continuation); - li.add(songinfo); -} } + if (contents[i]["musicResponsiveListItemRenderer"]["flexColumns"][0] + ["musicResponsiveListItemFlexColumnRenderer"]["text"]["runs"] + [0]["navigationEndpoint"] + .containsKey('watchEndpoint')) { + SongInfo songinfo = SongInfo( + title: contents[i]["musicResponsiveListItemRenderer"]["flexColumns"][0]["musicResponsiveListItemFlexColumnRenderer"] + ["text"]["runs"][0]["text"], + videoId: contents[i]["musicResponsiveListItemRenderer"]["flexColumns"][0]["musicResponsiveListItemFlexColumnRenderer"]["text"] + ["runs"][0]["navigationEndpoint"]["watchEndpoint"]["videoId"], + duration: contents[i]["musicResponsiveListItemRenderer"]["flexColumns"][1]["musicResponsiveListItemFlexColumnRenderer"]["text"]["runs"].length - 1 > 0 + ? contents[i]["musicResponsiveListItemRenderer"]["flexColumns"][1] + ["musicResponsiveListItemFlexColumnRenderer"]["text"]["runs"] + [contents[i]["musicResponsiveListItemRenderer"]["flexColumns"][1]["musicResponsiveListItemFlexColumnRenderer"]["text"]["runs"].length - 1]["text"] + : '0:00', + artists: contents[i]["musicResponsiveListItemRenderer"]["flexColumns"][1]["musicResponsiveListItemFlexColumnRenderer"]["text"]["runs"][0]["text"], + thumbnails: contents[i]["musicResponsiveListItemRenderer"]["thumbnail"]["musicThumbnailRenderer"]["thumbnail"]["thumbnails"][0]["url"], + continuations: continuation); + li.add(songinfo); + } + } return li; } } diff --git a/lib/services/api/search_suggestions_api.dart b/lib/services/api/search_suggestions_api.dart index 15cf36d..abfe469 100644 --- a/lib/services/api/search_suggestions_api.dart +++ b/lib/services/api/search_suggestions_api.dart @@ -1,46 +1,20 @@ -import 'dart:convert'; -import 'package:http/http.dart' as http; +import 'package:musico/constants/api_url.dart'; import 'package:musico/models/requests/search_suggestion.dart'; +import 'package:musico/utils/client.dart'; class SearchSuggestionApi { - final Map _header = { - "Accept": "*/*", - "Host": "www.youtube.com", - "Content-Type": "application/json", - "X-Goog-Api-Key": "AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8", - "User-Agent": - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36 Edg/105.0.1343.42", - "Origin": "https://www.youtube.com", - "Referer": "https://www.youtube.com/", - "Accept-Encoding": "gzip, deflate", - "Accept-Language": "de,de-DE;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6", - }; - - final String _data = jsonEncode({ - "context": { - "client": { - "hl": "en", - "gl": "US", - "clientName": "IOS_MUSIC", - "clientVersion": "5.26.1", - "clientScreen": "WATCH", - "androidSdkVersion": 31 - }, - "thirdParty": {"embedUrl": "https://www.youtube.com/"} - }, - "playbackContext": { - "contentPlaybackContext": {"signatureTimestamp": 19250} - }, - "racyCheckOk": true, - "contentCheckOk": true - }); + ApiClient client = ApiClient(); Future?> getSuggestion(String q) async { - final response = await http.post( - Uri.parse( - "https://music.youtube.com/youtubei/v1/music/get_search_suggestions?key=AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8&input=$q&fields=contents.searchSuggestionsSectionRenderer.contents(searchSuggestionRenderer.navigationEndpoint.searchEndpoint,musicTwoColumnItemRenderer(navigationEndpoint.watchEndpoint.videoId,subtitle,title,thumbnail.musicThumbnailRenderer.thumbnail))"), - headers: _header, - body: _data); + final response = await client.post(searchSuggestionUrl, + queryParamMap: { + "key": "AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8", + "input": q, + "fields": + "contents.searchSuggestionsSectionRenderer.contents(searchSuggestionRenderer.navigationEndpoint.searchEndpoint,musicTwoColumnItemRenderer(navigationEndpoint.watchEndpoint.videoId,subtitle,title,thumbnail.musicThumbnailRenderer.thumbnail))" + }, + ytClientName: "IOS_MUSIC"); + try { SearchSuggestionReq suggestionlist = searchSuggestionReqFromJson(response.body.toString()); diff --git a/lib/services/audio_handler.dart b/lib/services/audio_handler.dart new file mode 100644 index 0000000..2604583 --- /dev/null +++ b/lib/services/audio_handler.dart @@ -0,0 +1,27 @@ +import 'package:audio_service/audio_service.dart'; +import 'package:just_audio/just_audio.dart'; + +class AudioPlayerHandler extends BaseAudioHandler with SeekHandler, QueueHandler { + final _player = AudioPlayer(); + final _songQueue = ConcatenatingAudioSource(children: []); + + @override + Future play() => _player.play(); + + @override + Future pause() => _player.pause(); + + @override + Future stop() => _player.stop(); + + @override + Future seek(Duration position) => _player.seek(position); + + @override + Future skipToQueueItem(int index) => _player.seek(Duration.zero, index: index); + + @override + Future addQueueItem(MediaItem mediaItem) async { + + } +} \ No newline at end of file diff --git a/lib/utils/client.dart b/lib/utils/client.dart new file mode 100644 index 0000000..8025f3c --- /dev/null +++ b/lib/utils/client.dart @@ -0,0 +1,95 @@ +import 'dart:convert'; + +import 'package:http/http.dart' as http; + +class ApiClient { + final http.Client _client; + + final Map _headers = { + "Accept": "*/*", + "Host": "www.youtube.com", + "X-Goog-Api-Key": "AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8", + "Content-Type": "application/json", + "User-Agent": + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36 Edg/105.0.1343.42", + "Origin": "https://www.youtube.com", + "Referer": "https://www.youtube.com/", + "Accept-Encoding": "gzip, deflate", + "Accept-Language": "de,de-DE;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6" + }; + + final Map _data = { + "context": { + "client": { + "hl": "en", + "gl": "US", + "clientName": "WEB_REMIX", + "clientVersion": "1.20220918", + "clientScreen": "WATCH", + "androidSdkVersion": 31 + } + }, + }; + + Map ytClients = { + "WEB_REMIX": {"clientName": "WEB_REMIX", "clientVersion": "1.20220918"}, + "ANDROID_MUSIC": {"clientName": "ANDROID_MUSIC", "clientVersion": "6.01"}, + "IOS_MUSIC": {"clientName": "IOS_MUSIC", "clientVersion": "6.01"}, + "ANDROID_VR": {"clientName": "ANDROID_VR", "clientVersion": "1.56.21"} + }; + + ApiClient() : _client = http.Client(); + + Future get(String uri, + [Map? queryParamMap]) async { + try { + if (queryParamMap != null && queryParamMap.isNotEmpty) { + uri += _getQueryParams(queryParamMap); + } + + final response = await _client.get(Uri.parse(uri), headers: _headers); + return jsonDecode(response.body.toString()); + } catch (e) { + throw Exception('Failed to load data: $e'); + } + } + + Future post(String uri, + {Map? queryParamMap, + Map? data, + Map? optHeaders, + required String ytClientName}) async { + try { + if (queryParamMap != null && queryParamMap.isNotEmpty) { + uri += _getQueryParams(queryParamMap); + } + + if (ytClientName.isNotEmpty) { + _data["context"]["client"]["clientName"] = + ytClients[ytClientName]["clientName"]; + _data["context"]["client"]["clientVersion"] = + ytClients[ytClientName]["clientVersion"]; + } + + final response = await _client.post(Uri.parse(uri), + headers: + optHeaders != null && optHeaders.isNotEmpty ? optHeaders : {}, + body: data != null && data.isNotEmpty + ? jsonEncode(data) + : jsonEncode(_data)); + + return response; + } catch (e) { + throw Exception('Failed to load data: $e'); + } + } + + String _getQueryParams(Map queryParamMap) { + final queryString = Uri(queryParameters: queryParamMap).query; + return queryString.isNotEmpty ? '?$queryString' : ''; + } + + void dispose() { + _client.close(); + } +} diff --git a/lib/widgets/floating_mediabar.dart b/lib/widgets/floating_mediabar.dart new file mode 100644 index 0000000..b81a110 --- /dev/null +++ b/lib/widgets/floating_mediabar.dart @@ -0,0 +1,124 @@ +import 'package:flutter/material.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:musico/colors/color.dart'; +import 'package:musico/screens/player_screen.dart'; +import 'package:musico/services/Providers/music_player_provider.dart'; +import 'package:page_transition/page_transition.dart'; +import 'package:provider/provider.dart'; + +class FloatingMediaBar extends StatefulWidget { + const FloatingMediaBar({super.key}); + + @override + State createState() => _FloatingMediaBarState(); +} + +class _FloatingMediaBarState extends State { + @override + Widget build(BuildContext context) { + return Consumer( + builder: (_, musicPlayerProvider, child) { + return Visibility( + visible: musicPlayerProvider.audio == null || + musicPlayerProvider.songs == [] + ? false + : true, + child: Container( + alignment: Alignment.center, + margin: const EdgeInsets.symmetric(horizontal: 10.0), + decoration: BoxDecoration( + color: primaryThemeColor, + borderRadius: BorderRadius.circular(10.0)), + width: MediaQuery.of(context).size.width * 0.95, + child: GestureDetector( + onVerticalDragEnd: (details) async { + await musicPlayerProvider.advancedPlayer.stop(); + musicPlayerProvider.audio = null; + musicPlayerProvider.isNewSongSet = true; + }, + child: ClipRRect( + borderRadius: BorderRadius.circular(12.0), + child: ListTile( + title: Text( + musicPlayerProvider.songs.isNotEmpty + ? musicPlayerProvider + .songs[musicPlayerProvider.currentIndex].title + : "", + maxLines: 1, + style: GoogleFonts.poppins( + color: Colors.black, fontWeight: FontWeight.w600), + overflow: TextOverflow.ellipsis, + ), + subtitle: Text( + musicPlayerProvider.songs.isNotEmpty + ? musicPlayerProvider + .songs[musicPlayerProvider.currentIndex].author + : "", + maxLines: 1, + style: GoogleFonts.poppins( + color: Colors.black, fontWeight: FontWeight.w500), + overflow: TextOverflow.ellipsis, + ), + leading: ClipRRect( + borderRadius: BorderRadius.circular(5.0), + child: Image( + image: NetworkImage(musicPlayerProvider.songs.isNotEmpty + ? musicPlayerProvider + .songs[musicPlayerProvider.currentIndex].thumbnail + : ""), + ), + ), + trailing: Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.end, + children: [ + IconButton( + iconSize: 35, + icon: musicPlayerProvider.isPlaying + ? const Icon( + Icons.pause, + size: 35, + color: Colors.black, + ) + : const Icon( + Icons.play_arrow, + size: 35, + color: Colors.black, + ), + onPressed: () { + musicPlayerProvider.togglePlayback(); + }, + ), + Visibility( + visible: musicPlayerProvider.advancedPlayer.hasNext + ? true + : false, + child: IconButton( + iconSize: 35, + icon: const Icon( + Icons.skip_next, + color: Colors.black, + ), + onPressed: () { + musicPlayerProvider.playNextSong(); + }, + ), + ), + ], + ), + ), + ), + onTap: () { + Navigator.push( + context, + PageTransition( + child: const PlayerScreen(), + type: PageTransitionType.bottomToTop)); + }, + ), + ), + ); + }, + ); + } +} diff --git a/lib/components/song_card.dart b/lib/widgets/song_card.dart similarity index 100% rename from lib/components/song_card.dart rename to lib/widgets/song_card.dart diff --git a/pubspec.lock b/pubspec.lock index aa26e5a..24337c6 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -286,6 +286,14 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_animate: + dependency: "direct main" + description: + name: flutter_animate + sha256: "7c8a6594a9252dad30cc2ef16e33270b6248c4dedc3b3d06c86c4f3f4dc05ae5" + url: "https://pub.dev" + source: hosted + version: "4.5.0" flutter_cache_manager: dependency: transitive description: @@ -366,6 +374,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.3.11" + flutter_shaders: + dependency: transitive + description: + name: flutter_shaders + sha256: "02750b545c01ff4d8e9bbe8f27a7731aa3778402506c67daa1de7f5fc3f4befe" + url: "https://pub.dev" + source: hosted + version: "0.1.2" flutter_test: dependency: "direct dev" description: flutter @@ -408,6 +424,22 @@ packages: url: "https://pub.dev" source: hosted version: "5.1.0" + hive: + dependency: "direct main" + description: + name: hive + sha256: "8dcf6db979d7933da8217edcec84e9df1bdb4e4edc7fc77dbd5aa74356d6d941" + url: "https://pub.dev" + source: hosted + version: "2.2.3" + hive_flutter: + dependency: "direct main" + description: + name: hive_flutter + sha256: dca1da446b1d808a51689fb5d0c6c9510c0a2ba01e22805d492c73b68e33eecc + url: "https://pub.dev" + source: hosted + version: "1.1.0" html: dependency: transitive description: @@ -745,7 +777,7 @@ packages: source: hosted version: "3.4.0" shared_preferences: - dependency: "direct main" + dependency: transitive description: name: shared_preferences sha256: d3bbe5553a986e83980916ded2f0b435ef2e1893dfaa29d5a7a790d0eca12180 diff --git a/pubspec.yaml b/pubspec.yaml index 287f6db..41f878b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -39,11 +39,14 @@ dependencies: dio: ^5.3.2 flutter: sdk: flutter + flutter_animate: ^4.5.0 flutter_native_splash: ^2.3.1 flutter_typeahead: ^5.2.0 font_awesome_flutter: ^10.5.0 get: ^4.6.5 google_fonts: ^5.1.0 + hive: ^2.2.3 + hive_flutter: ^1.1.0 http: ^1.1.0 just_audio: ^0.9.40 just_audio_background: ^0.0.1-beta.13 @@ -52,7 +55,6 @@ dependencies: page_transition: ^2.0.9 provider: ^6.0.5 share_plus: ^7.1.0 - shared_preferences: ^2.2.2 shimmer: ^3.0.0 speech_to_text: ^6.3.0 upgrader: ^8.4.0