Skip to content

Instantly share code, notes, and snippets.

@jaceshim
Last active July 2, 2020 10:58
Show Gist options
  • Save jaceshim/eaf78b8b6e3a94ee1d79c3f56ac839af to your computer and use it in GitHub Desktop.
Save jaceshim/eaf78b8b6e3a94ee1d79c3f56ac839af to your computer and use it in GitHub Desktop.
import 'dart:async';
import 'dart:convert';
import 'package:html/parser.dart';
import 'package:http/http.dart' as http;
class VideoService extends BaseService {
final KeyStorageService _keyStorageService = locator<KeyStorageService>();
Future<List<Channel>> getChannels(String searchQuery) async {
final List<Channel> result = [];
try {
final Map<String, dynamic> data =
await getYoutubeContents("/results?sp=EgIQAg%253D%253D&search_query=${Uri.encodeComponent(searchQuery)}");
if (data != null) {
List<dynamic> channelDataList =
data["contents"]["sectionListRenderer"]["contents"][0]["itemSectionRenderer"]["contents"];
channelDataList.forEach((channelData) {
var channelRenderer = channelData["compactChannelRenderer"];
String channelId = channelRenderer["channelId"];
String thumbnail = channelRenderer["thumbnail"]["thumbnails"][0]["url"];
if (thumbnail.startsWith("//")) {
thumbnail = "https:${thumbnail}";
}
String displayName = channelRenderer["displayName"]["runs"][0]["text"];
var videoCountTextData = channelRenderer["videoCountText"]["runs"];
String videoCountText =
"${videoCountTextData[0]["text"]} ${videoCountTextData[1]["text"]} ${videoCountTextData[2]["text"]}";
String subscriberCountText = channelRenderer["subscriberCountText"]["runs"][0]["text"];
result.add(Channel(
channelId: channelId,
thumbnail: thumbnail,
displayName: displayName,
videoCountText: videoCountText,
subscriberCountText: subscriberCountText,
));
});
}
} catch (e) {
print("get videos error : $e");
}
return result;
}
Future<List<Video>> getChannelVideos() async {
final List<Channel> channels = _keyStorageService.subscriptionChannels ?? MemoryStorageService().defaultChannels;
final List<Video> result = [];
var futures = <Future>[];
try {
channels.forEach((channel) {
futures.add(getYoutubeContents("/channel/${channel.channelId}/videos").then((data) {
if (data != null) {
List<dynamic> videoDataList = data["contents"]["singleColumnBrowseResultsRenderer"]["tabs"][1]
["tabRenderer"]["content"]["sectionListRenderer"]["contents"][0]["itemSectionRenderer"]["contents"];
Future.forEach(videoDataList, (videoData) async {
try {
final Video video = _parseVideoData(videoData);
result.add(video);
} catch (e) {
print("$videoData : extract url error : $e");
}
});
}
}));
});
} catch (e) {
print("get videos error : $e");
}
await Future.wait(futures);
return result;
}
Future<List<Video>> searchVideos(String searchQuery) async {
final List<Video> result = [];
try {
Map<String, dynamic> data = await getYoutubeContents("/results?search_query=${Uri.encodeComponent(searchQuery)}");
if (data != null) {
List<dynamic> videoDataList =
data["contents"]["sectionListRenderer"]["contents"][0]["itemSectionRenderer"]["contents"];
Future.forEach(videoDataList, (videoData) async {
try {
final Video video = _parseVideoData(videoData);
result.add(video);
} catch (e) {
print("$videoData : extract url error : $e");
}
});
}
} catch (e) {
print("get videos error : $e");
}
return result;
}
static final String youtubeUrl = "https://m.youtube.com";
Future<Map<String, dynamic>> getYoutubeContents(String uri) async {
final String url = "${youtubeUrl}${uri}";
Map<String, dynamic> result;
try {
var response = await http.get(
url,
headers: {
"user-agent":
"Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Mobile Safari/537.36",
},
);
if (response.statusCode == 200) {
final String htmlString = response.body;
var document = parse(htmlString);
String initData = document.querySelector("#initial-data").nodes[0].text;
initData = initData.replaceAll("<!--", "").replaceAll("-->", "").trim();
result = jsonDecode(initData);
}
} catch (e) {
print("$url => get html error : $e");
}
return result;
}
Video _parseVideoData(Map<String, dynamic> videoData) {
Map<String, dynamic> data = videoData["compactVideoRenderer"];
String videoId = data["videoId"];
String title = data["title"]["runs"][0]["text"];
String thumbnail = "https://i.ytimg.com/vi_webp/${videoId}/sddefault.webp";
String viewCountText = data["viewCountText"]["runs"][0]["text"];
String lengthText = data["lengthText"]["runs"][0]["text"];
String publishedTimeText = "";
try {
publishedTimeText = data["publishedTimeText"]["runs"][0]["text"];
} catch (e) {
print("parse publishedTimeText error: $e");
}
// String videoPlayUrl = await CommonUtils.extractYoutubeUrl(videoId);
return Video(
videoId: videoId,
title: title,
thumbnail: thumbnail,
publishedTimeText: publishedTimeText,
viewCountText: viewCountText,
lengthText: lengthText,
// videoPlayUrl: videoPlayUrl,
);
}
Future<List<Video>> getNextVideos(String videoId) async {
final List<Video> result = [];
try {
Map<String, dynamic> data = await getYoutubeContents("/watch?v=${videoId}");
if (data != null) {
List<dynamic> contents = data["contents"]["singleColumnWatchNextResults"]["results"]["results"]["contents"];
for (Map<String, dynamic> content in contents) {
bool hasKey = content.containsKey("itemSectionRenderer");
if (hasKey) {
List<dynamic> videoDataList = content["itemSectionRenderer"]["contents"];
videoDataList = videoDataList.sublist(1, videoDataList.length);
videoDataList.forEach((videoData) {
try {
final Video video = _parseVideoData(videoData);
result.add(video);
} catch (e) {
print("$videoData : extract url error : $e");
}
});
}
}
}
} catch (e) {
print("get videos error : $e");
}
return result;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment