Skip to content

Commit

Permalink
youtube charts on home page
Browse files Browse the repository at this point in the history
  • Loading branch information
HemantKArya committed Mar 20, 2024
1 parent cce3e35 commit 7beb52d
Show file tree
Hide file tree
Showing 12 changed files with 291 additions and 65 deletions.
16 changes: 16 additions & 0 deletions lib/blocs/explore/cubit/explore_cubit.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import 'package:Bloomee/repository/Youtube/yt_charts_home.dart';
import 'package:bloc/bloc.dart';

part 'explore_state.dart';

class ExploreCubit extends Cubit<ExploreState> {
ExploreCubit() : super(ExploreInitial()) {
getTrendingVideos();
}

void getTrendingVideos() async {
final ytCharts = await fetchTrendingVideos();

emit(state.copyWith(ytCharts: ytCharts));
}
}
21 changes: 21 additions & 0 deletions lib/blocs/explore/cubit/explore_state.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// ignore_for_file: public_member_api_docs, sort_constructors_first
part of 'explore_cubit.dart';

class ExploreState {
List<List<Map<String, dynamic>>> ytCharts = List.empty(growable: true);
ExploreState({
required this.ytCharts,
});

ExploreState copyWith({
List<List<Map<String, dynamic>>>? ytCharts,
}) {
return ExploreState(
ytCharts: ytCharts ?? this.ytCharts,
);
}
}

final class ExploreInitial extends ExploreState {
ExploreInitial() : super(ytCharts: []);
}
1 change: 0 additions & 1 deletion lib/model/yt_music_model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ MediaItemModel fromYtSongMap2MediaItem(Map<dynamic, dynamic> songItem) {
artistsID.add(element["id"]);
});
artists = _artists.join(',');
print(songItem["duration"]);
return MediaItemModel(
id: songItem["id"] ?? 'Unknown',
title: songItem["title"] ?? 'Unknown',
Expand Down
80 changes: 80 additions & 0 deletions lib/repository/Youtube/yt_charts_home.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import 'package:http/http.dart' as http;
import 'dart:convert';

Future<List<List<Map<String, dynamic>>>> fetchTrendingVideos() async {
// Fetch the YouTube page to extract the INNERTUBE_API_KEY
var response = await http
.get(Uri.parse('https://charts.youtube.com/charts/TrendingVideos/gb'));
final keyRegex = RegExp(r'"INNERTUBE_API_KEY"\s*:\s*"(.*?)"');
final apiKey = keyRegex.firstMatch(response.body)?.group(1);

if (apiKey == null) {
throw Exception('Failed to extract INNERTUBE_API_KEY');
}

// Prepare the headers and data for the POST request
final headers = {
'referer': 'https://charts.youtube.com/charts/TrendingVideos/gb',
};

final data = {
"context": {
"client": {
"clientName": "WEB_MUSIC_ANALYTICS",
"clientVersion": "2.0",
"hl": "en",
"gl": "AR",
"experimentIds": [],
"experimentsToken": "",
"theme": "MUSIC"
},
"capabilities": {},
"request": {"internalExperimentFlags": []}
},
"browseId": "FEmusic_analytics_charts_home",
"query": "perspective=CHART_HOME&chart_params_country_code=global"
};

// Make the POST request
response = await http.post(
Uri.parse(
'https://charts.youtube.com/youtubei/v1/browse?alt=json&key=$apiKey'),
headers: headers,
body: json.encode(data),
);

if (response.statusCode == 200) {
// Parse the JSON response
// return json.decode(response.body);
List<dynamic> data = json.decode(response.body)["contents"]
['sectionListRenderer']["contents"][0]
['musicAnalyticsSectionRenderer']['content']['videos'];

List<List<Map<String, dynamic>>> playlists = [];

for (var types in data) {
List<Map<String, dynamic>> playlist = [];
for (var i in types['videoViews']) {
String title = i['title'];
String views = i['viewCount'];
String id = i['id'];
String img = i['thumbnail']['thumbnails'][0]['url'];
List<String> artists = [];
for (var artist in i['artists']) {
artists.add(artist['name']);
}
playlist.add({
'title': title,
'views': views,
'id': id,
'img': img,
'artists': artists
});
}
playlists.add(playlist);
}
return playlists;
} else {
throw Exception('Failed to load data: ${response.statusCode}');
}
}
7 changes: 4 additions & 3 deletions lib/repository/Youtube/yt_music_api.dart
Original file line number Diff line number Diff line change
Expand Up @@ -260,14 +260,14 @@ class YtMusicService {
final List result = data['contents']['twoColumnBrowseResultsRenderer']
['tabs'][0]['tabRenderer']['content']['sectionListRenderer']
['contents'] as List;

// dev.log("result: $result", name: "YTM");
final List headResult = data['header']['carouselHeaderRenderer']
['contents'][0]['carouselItemRenderer']['carouselItems'] as List;

final List shelfRenderer = result.map((element) {
return element['itemSectionRenderer']['contents'][0]['shelfRenderer'];
}).toList();

// dev.log("${shelfRenderer.toString()}");
final List finalResult = [];

for (Map element in shelfRenderer) {
Expand All @@ -287,6 +287,7 @@ class YtMusicService {
name: "YTM");
}
}
// dev.log("finalResult: $finalResult", name: "YTM");

final List finalHeadResult = formatHeadItems(headResult);
finalResult.removeWhere((element) => element == null);
Expand Down Expand Up @@ -882,7 +883,7 @@ class YtMusicService {
};
// pprint(cToken);
final response = await sendRequest(endpoint, body, headers, params: params);
print(response);
// print(response);
}

Future<Map> getAlbumDetails(String albumId) async {
Expand Down
74 changes: 74 additions & 0 deletions lib/repository/Youtube/yt_music_home.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import 'dart:convert';
import 'dart:developer';
import 'package:http/http.dart' as http;

String decodeEscapeSequences(String encodedString) {
return encodedString.replaceAllMapped(RegExp(r'\\x([0-9a-fA-F]{2})'),
(match) => String.fromCharCode(int.parse(match.group(1)!, radix: 16)));
}

Future<List<dynamic>> fetchMusicData() async {
final client = http.Client();
final headers = {
'User-Agent':
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36',
'Sec-Ch-Ua':
'"Chromium";v="94", "Google Chrome";v="94", ";Not A Brand";v="99"',
'Sec-Ch-Ua-Mobile': '?0',
'Sec-Ch-Ua-Platform': '"Windows"',
};

final response = await client.get(Uri.parse('https://music.youtube.com/'),
headers: headers);
if (response.statusCode == 200) {
final html = response.body;
final pattern = RegExp(r"data:\s*'\\x7b(.*?)'");
final matches = pattern.allMatches(html);
if (matches.isNotEmpty) {
final encodedString =
r'\x7b'.toString() + matches.first.group(1).toString();
final decodedBytes =
utf8.decode(decodeEscapeSequences(encodedString).codeUnits);

final Map<String, dynamic> data = jsonDecode(decodedBytes);
// log(data.keys.toString(), name: "YT Music Home Data Keys");
final items = [];
final contents = data['contents']['singleColumnBrowseResultsRenderer']
['tabs'][0]['tabRenderer']['content']['sectionListRenderer']
['contents'][0]['musicCarouselShelfRenderer']['contents'];

for (var content in contents) {
final img = content['musicResponsiveListItemRenderer']['thumbnail']
['musicThumbnailRenderer']["thumbnail"]["thumbnails"][0]["url"];
final title = content['musicResponsiveListItemRenderer']['flexColumns']
[0]['musicResponsiveListItemFlexColumnRenderer']["text"]["runs"]
[0]["text"];
final watchid = content['musicResponsiveListItemRenderer']
['flexColumns'][0]
['musicResponsiveListItemFlexColumnRenderer']["text"]["runs"][0]
["navigationEndpoint"]["watchEndpoint"]["videoId"];
final playlistid = content['musicResponsiveListItemRenderer']
['flexColumns'][0]
['musicResponsiveListItemFlexColumnRenderer']["text"]["runs"][0]
["navigationEndpoint"]["watchEndpoint"]["playlistId"];
var artists = '';
for (var artist in content['musicResponsiveListItemRenderer']
['flexColumns'][1]['musicResponsiveListItemFlexColumnRenderer']
["text"]["runs"]) {
artists += artist["text"];
}
items.add({
"title": title,
"img": img,
"watchid": watchid,
"playlistid": playlistid,
"artists": artists
});
}

return items;
}
}

throw Exception('Failed to load');
}
4 changes: 2 additions & 2 deletions lib/repository/Youtube/ytmusic_format.dart
Original file line number Diff line number Diff line change
Expand Up @@ -144,9 +144,9 @@ Future<List> formatHomeSections(List items) async {
'count': e['compactStationRenderer']['videoCountText']['runs'][0]
['text'],
'id':
'youtube${e['compactStationRenderer']['navigationEndpoint']['watchEndpoint']['playlistId']}',
'youtube${e['compactStationRenderer']['navigationEndpoint']['watchPlaylistEndpoint']['playlistId']}',
'firstItemId':
'youtube${e['compactStationRenderer']['navigationEndpoint']['watchEndpoint']['videoId']}',
'youtube${e['compactStationRenderer']['navigationEndpoint']['watchPlaylistEndpoint']['videoId']}',
'image': e['compactStationRenderer']['thumbnail']['thumbnails'][0]
['url'],
'images': [
Expand Down
1 change: 1 addition & 0 deletions lib/screens/screen/chart/chart_view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ class _ChartScreenState extends State<ChartScreen> {
} else {
final List<Map<String, String>>? melon = snapshot.data;
return CustomScrollView(
physics: const BouncingScrollPhysics(),
slivers: [
customDiscoverBar(context), //AppBar
SliverList(
Expand Down
41 changes: 23 additions & 18 deletions lib/screens/screen/explore_screen.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import 'package:Bloomee/blocs/explore/cubit/explore_cubit.dart';
import 'package:Bloomee/services/db/cubit/bloomee_db_cubit.dart';
import 'package:Bloomee/utils/app_updater.dart';
import 'package:flutter/material.dart';
Expand Down Expand Up @@ -33,25 +34,29 @@ class _ExploreScreenState extends State<ExploreScreen> {

@override
Widget build(BuildContext context) {
return Scaffold(
body: CustomScrollView(
physics: const BouncingScrollPhysics(),
slivers: [
customDiscoverBar(context), //AppBar
SliverList(
delegate: SliverChildListDelegate([
Padding(
padding: const EdgeInsets.only(top: 20),
child: CaraouselWidget(),
),
const Padding(
padding: EdgeInsets.only(top: 20),
child: TabSongListWidget(),
),
]))
],
return BlocProvider(
create: (context) => ExploreCubit(),
lazy: false,
child: Scaffold(
body: CustomScrollView(
physics: const BouncingScrollPhysics(),
slivers: [
customDiscoverBar(context), //AppBar
SliverList(
delegate: SliverChildListDelegate([
Padding(
padding: const EdgeInsets.only(top: 20),
child: CaraouselWidget(),
),
const Padding(
padding: EdgeInsets.only(top: 20),
child: TabSongListWidget(),
),
]))
],
),
backgroundColor: Default_Theme.themeColor,
),
backgroundColor: Default_Theme.themeColor,
);
}

Expand Down
37 changes: 21 additions & 16 deletions lib/screens/widgets/chart_list_tile.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,25 +22,30 @@ class ChartListTile extends StatelessWidget {
return InkWell(
onTap: () => context.push(
"/${GlobalStrConsts.searchScreen}?query=${title} by ${subtitle}"),
child: ListTile(
leading: loadImageCached(imgUrl),
title: Text(
title,
textAlign: TextAlign.start,
overflow: TextOverflow.ellipsis,
maxLines: 1,
style: Default_Theme.tertiaryTextStyle.merge(const TextStyle(
fontWeight: FontWeight.w600,
color: Default_Theme.primaryColor1,
fontSize: 14)),
),
subtitle: Text(subtitle,
child: SizedBox(
width: 300,
child: ListTile(
leading: ClipRRect(
borderRadius: BorderRadius.circular(10),
child: SizedBox(height: 60, child: loadImageCached(imgUrl))),
title: Text(
title,
textAlign: TextAlign.start,
overflow: TextOverflow.ellipsis,
maxLines: 1,
style: Default_Theme.tertiaryTextStyle.merge(TextStyle(
color: Default_Theme.primaryColor1.withOpacity(0.8),
fontSize: 13))),
style: Default_Theme.tertiaryTextStyle.merge(const TextStyle(
fontWeight: FontWeight.w600,
color: Default_Theme.primaryColor1,
fontSize: 14)),
),
subtitle: Text(subtitle,
textAlign: TextAlign.start,
overflow: TextOverflow.ellipsis,
maxLines: 1,
style: Default_Theme.tertiaryTextStyle.merge(TextStyle(
color: Default_Theme.primaryColor1.withOpacity(0.8),
fontSize: 13))),
),
),
);
}
Expand Down
Loading

0 comments on commit 7beb52d

Please sign in to comment.