Code refactoring
This commit is contained in:
parent
3b530c7ee4
commit
ec80924a28
|
@ -1,11 +1,12 @@
|
|||
import 'package:youtube_explode_dart/src/channels/channel_uploads_list.dart';
|
||||
import 'package:youtube_explode_dart/src/reverse_engineering/pages/channel_page.dart';
|
||||
import 'package:youtube_explode_dart/src/reverse_engineering/responses/video_info_response.dart';
|
||||
|
||||
import '../common/common.dart';
|
||||
import '../extensions/helpers_extension.dart';
|
||||
import '../playlists/playlists.dart';
|
||||
import '../reverse_engineering/responses/channel_about_page.dart';
|
||||
import '../reverse_engineering/responses/channel_upload_page.dart';
|
||||
import '../reverse_engineering/responses/responses.dart';
|
||||
import '../reverse_engineering/pages/channel_about_page.dart';
|
||||
import '../reverse_engineering/pages/channel_upload_page.dart';
|
||||
import '../reverse_engineering/youtube_http_client.dart';
|
||||
import '../videos/video.dart';
|
||||
import '../videos/video_id.dart';
|
||||
|
@ -55,16 +56,16 @@ class ChannelClient {
|
|||
final aboutPage = await ChannelAboutPage.get(_httpClient, channelId.value);
|
||||
final id = aboutPage.initialData;
|
||||
return ChannelAbout(
|
||||
id.description,
|
||||
id.viewCount,
|
||||
id.joinDate,
|
||||
id.title,
|
||||
aboutPage.description,
|
||||
aboutPage.viewCount,
|
||||
aboutPage.joinDate,
|
||||
aboutPage.title,
|
||||
[
|
||||
for (var e in id.avatar)
|
||||
for (var e in aboutPage.avatar)
|
||||
Thumbnail(Uri.parse(e['url']), e['height'], e['width'])
|
||||
],
|
||||
id.country,
|
||||
id.channelLinks);
|
||||
aboutPage.country,
|
||||
aboutPage.channelLinks);
|
||||
}
|
||||
|
||||
/// Gets the info found on a YouTube Channel About page.
|
||||
|
@ -125,7 +126,7 @@ class ChannelClient {
|
|||
final channel = await get(channelId);
|
||||
|
||||
return ChannelUploadsList(
|
||||
page.initialData.uploads
|
||||
page.uploads
|
||||
.map((e) => Video(
|
||||
e.videoId,
|
||||
e.videoTitle,
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:youtube_explode_dart/src/reverse_engineering/responses/channel_upload_page.dart';
|
||||
import 'package:youtube_explode_dart/src/reverse_engineering/pages/channel_upload_page.dart';
|
||||
|
||||
import '../../youtube_explode_dart.dart';
|
||||
import '../extensions/helpers_extension.dart';
|
||||
|
|
|
@ -5,6 +5,8 @@ import 'package:collection/collection.dart';
|
|||
|
||||
import '../reverse_engineering/cipher/cipher_operations.dart';
|
||||
|
||||
typedef JsonMap = Map<String, dynamic>;
|
||||
|
||||
/// Utility for Strings.
|
||||
extension StringUtility on String {
|
||||
/// Returns null if this string is whitespace.
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import 'package:equatable/equatable.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
import '../common/common.dart';
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import 'package:youtube_explode_dart/src/channels/channel_id.dart';
|
||||
import 'package:youtube_explode_dart/src/reverse_engineering/responses/playlist_page.dart';
|
||||
import 'package:youtube_explode_dart/src/reverse_engineering/pages/playlist_page.dart';
|
||||
|
||||
import '../common/common.dart';
|
||||
import '../reverse_engineering/youtube_http_client.dart';
|
||||
|
@ -22,25 +22,51 @@ class PlaylistClient {
|
|||
var response = await PlaylistPage.get(_httpClient, id.value);
|
||||
return Playlist(
|
||||
id,
|
||||
response.initialData.title ?? '',
|
||||
response.initialData.author ?? '',
|
||||
response.initialData.description ?? '',
|
||||
response.title ?? '',
|
||||
response.author ?? '',
|
||||
response.description ?? '',
|
||||
ThumbnailSet(id.value),
|
||||
Engagement(response.initialData.viewCount ?? 0, null, null));
|
||||
Engagement(response.viewCount ?? 0, null, null));
|
||||
}
|
||||
|
||||
/// Enumerates videos included in the specified playlist.
|
||||
Stream<Video> getVideos(dynamic id) async* {
|
||||
id = PlaylistId.fromString(id);
|
||||
var encounteredVideoIds = <String>{};
|
||||
String? continuationToken = '';
|
||||
final encounteredVideoIds = <String>{};
|
||||
|
||||
// ignore: literal_only_boolean_expressions
|
||||
while (true) {
|
||||
var response = await PlaylistPage.get(_httpClient, id.value,
|
||||
token: continuationToken);
|
||||
PlaylistPage? page = await PlaylistPage.get(_httpClient, id.value);
|
||||
|
||||
for (final video in response.initialData.playlistVideos) {
|
||||
for (final video in page.videos) {
|
||||
var videoId = video.id;
|
||||
|
||||
// Already added
|
||||
if (!encounteredVideoIds.add(videoId)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (video.channelId.isEmpty) {
|
||||
continue;
|
||||
}
|
||||
|
||||
yield Video(
|
||||
VideoId(videoId),
|
||||
video.title,
|
||||
video.author,
|
||||
ChannelId(video.channelId),
|
||||
null,
|
||||
null,
|
||||
video.description,
|
||||
video.duration,
|
||||
ThumbnailSet(videoId),
|
||||
null,
|
||||
Engagement(video.viewCount, null, null),
|
||||
false);
|
||||
}
|
||||
|
||||
page = await page.nextPage(_httpClient);
|
||||
|
||||
while (page != null) {
|
||||
for (final video in page.videos) {
|
||||
var videoId = video.id;
|
||||
|
||||
// Already added
|
||||
|
@ -66,10 +92,6 @@ class PlaylistClient {
|
|||
Engagement(video.viewCount, null, null),
|
||||
false);
|
||||
}
|
||||
continuationToken = response.initialData.continuationToken;
|
||||
if (response.initialData.continuationToken?.isEmpty ?? true) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import 'package:xml/xml.dart' as xml;
|
||||
|
||||
import '../../retry.dart';
|
||||
import '../youtube_http_client.dart';
|
||||
import 'stream_info_provider.dart';
|
||||
import '../retry.dart';
|
||||
import 'youtube_http_client.dart';
|
||||
import 'models/stream_info_provider.dart';
|
||||
|
||||
///
|
||||
class DashManifest {
|
|
@ -0,0 +1,10 @@
|
|||
import 'package:meta/meta.dart';
|
||||
|
||||
import '../../extensions/helpers_extension.dart';
|
||||
|
||||
abstract class InitialData {
|
||||
@protected
|
||||
final JsonMap root;
|
||||
|
||||
InitialData(this.root);
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
import 'package:html/dom.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
|
||||
import '../../../youtube_explode_dart.dart';
|
||||
import '../../extensions/helpers_extension.dart';
|
||||
import 'initial_data.dart';
|
||||
|
||||
abstract class YoutubePage<T extends InitialData> {
|
||||
@protected
|
||||
final Document? root;
|
||||
|
||||
@protected
|
||||
late final T initialData = _defaultInitialData ?? _getInitialData();
|
||||
|
||||
final T? _defaultInitialData;
|
||||
|
||||
@protected
|
||||
final T Function(JsonMap)? initialDataBuilder;
|
||||
|
||||
T _getInitialData() {
|
||||
final scriptText = root!
|
||||
.querySelectorAll('script')
|
||||
.map((e) => e.text)
|
||||
.toList(growable: false);
|
||||
return scriptText.extractGenericData(
|
||||
initialDataBuilder!,
|
||||
() => TransientFailureException(
|
||||
'Failed to retrieve initial data from $runtimeType, please report this to the project GitHub page.'));
|
||||
}
|
||||
|
||||
YoutubePage(this.root, this.initialDataBuilder, [this._defaultInitialData])
|
||||
: assert((root != null && initialDataBuilder != null) ||
|
||||
_defaultInitialData != null);
|
||||
}
|
|
@ -1,39 +1,39 @@
|
|||
import 'package:collection/collection.dart';
|
||||
import 'package:html/dom.dart';
|
||||
import 'package:html/parser.dart' as parser;
|
||||
import 'package:youtube_explode_dart/src/reverse_engineering/models/youtube_page.dart';
|
||||
|
||||
import '../../../youtube_explode_dart.dart';
|
||||
import '../../exceptions/exceptions.dart';
|
||||
import '../../extensions/helpers_extension.dart';
|
||||
import '../../retry.dart';
|
||||
import '../models/initial_data.dart';
|
||||
import '../youtube_http_client.dart';
|
||||
|
||||
///
|
||||
class ChannelAboutPage {
|
||||
final Document _root;
|
||||
|
||||
///
|
||||
late final _InitialData initialData = _getInitialData();
|
||||
|
||||
_InitialData _getInitialData() {
|
||||
final scriptText = _root
|
||||
.querySelectorAll('script')
|
||||
.map((e) => e.text)
|
||||
.toList(growable: false);
|
||||
return scriptText.extractGenericData(
|
||||
(obj) => _InitialData(obj),
|
||||
() => TransientFailureException(
|
||||
'Failed to retrieve initial data from the channel about page, please report this to the project GitHub page.'));
|
||||
}
|
||||
|
||||
class ChannelAboutPage extends YoutubePage<_InitialData> {
|
||||
///
|
||||
String get description => initialData.description;
|
||||
|
||||
///
|
||||
ChannelAboutPage(this._root);
|
||||
int get viewCount => initialData.viewCount;
|
||||
|
||||
///
|
||||
ChannelAboutPage.parse(String raw) : _root = parser.parse(raw);
|
||||
String get joinDate => initialData.joinDate;
|
||||
|
||||
///
|
||||
String get title => initialData.title;
|
||||
|
||||
///
|
||||
List<JsonMap> get avatar => initialData.avatar;
|
||||
|
||||
///
|
||||
String get country => initialData.country;
|
||||
|
||||
///
|
||||
List<ChannelLink> get channelLinks => initialData.channelLinks;
|
||||
|
||||
///
|
||||
ChannelAboutPage.parse(String raw)
|
||||
: super(parser.parse(raw), (root) => _InitialData(root));
|
||||
|
||||
///
|
||||
static Future<ChannelAboutPage> get(YoutubeHttpClient httpClient, String id) {
|
||||
|
@ -63,15 +63,12 @@ class ChannelAboutPage {
|
|||
|
||||
final _urlExp = RegExp(r'q=([^=]*)$');
|
||||
|
||||
class _InitialData {
|
||||
// Json parsed map
|
||||
final Map<String, dynamic> root;
|
||||
class _InitialData extends InitialData {
|
||||
late final JsonMap content = _getContentContext();
|
||||
|
||||
_InitialData(this.root);
|
||||
_InitialData(JsonMap root) : super(root);
|
||||
|
||||
late final Map<String, dynamic> content = _getContentContext();
|
||||
|
||||
Map<String, dynamic> _getContentContext() {
|
||||
JsonMap _getContentContext() {
|
||||
return root
|
||||
.get('contents')!
|
||||
.get('twoColumnBrowseResultsRenderer')!
|
||||
|
@ -123,7 +120,7 @@ class _InitialData {
|
|||
|
||||
late final String title = content.get('title')!.getT<String>('simpleText')!;
|
||||
|
||||
late final List<Map<String, dynamic>> avatar =
|
||||
late final List<JsonMap> avatar =
|
||||
content.get('avatar')!.getList('thumbnails')!;
|
||||
|
||||
late final String country =
|
|
@ -1,21 +1,20 @@
|
|||
import 'package:html/dom.dart';
|
||||
import 'package:html/parser.dart' as parser;
|
||||
import 'package:youtube_explode_dart/src/reverse_engineering/models/youtube_page.dart';
|
||||
|
||||
import '../../exceptions/exceptions.dart';
|
||||
import '../../extensions/helpers_extension.dart';
|
||||
import '../../retry.dart';
|
||||
import '../youtube_http_client.dart';
|
||||
import '../models/initial_data.dart';
|
||||
|
||||
///
|
||||
class ChannelPage {
|
||||
final Document _root;
|
||||
|
||||
class ChannelPage extends YoutubePage<_InitialData> {
|
||||
///
|
||||
bool get isOk => _root.querySelector('meta[property="og:url"]') != null;
|
||||
bool get isOk => root!.querySelector('meta[property="og:url"]') != null;
|
||||
|
||||
///
|
||||
String get channelUrl =>
|
||||
_root.querySelector('meta[property="og:url"]')?.attributes['content'] ??
|
||||
root!.querySelector('meta[property="og:url"]')?.attributes['content'] ??
|
||||
'';
|
||||
|
||||
///
|
||||
|
@ -23,35 +22,19 @@ class ChannelPage {
|
|||
|
||||
///
|
||||
String get channelTitle =>
|
||||
_root.querySelector('meta[property="og:title"]')?.attributes['content'] ??
|
||||
root!.querySelector('meta[property="og:title"]')?.attributes['content'] ??
|
||||
'';
|
||||
|
||||
///
|
||||
String get channelLogoUrl =>
|
||||
_root.querySelector('meta[property="og:image"]')?.attributes['content'] ??
|
||||
root!.querySelector('meta[property="og:image"]')?.attributes['content'] ??
|
||||
'';
|
||||
|
||||
int? get subscribersCount => initialData.subscribersCount;
|
||||
|
||||
///
|
||||
late final _InitialData initialData = _getInitialData();
|
||||
|
||||
_InitialData _getInitialData() {
|
||||
final scriptText = _root
|
||||
.querySelectorAll('script')
|
||||
.map((e) => e.text)
|
||||
.toList(growable: false);
|
||||
return scriptText.extractGenericData(
|
||||
(obj) => _InitialData(obj),
|
||||
() => TransientFailureException(
|
||||
'Failed to retrieve initial data from the channel about page, please report this to the project GitHub page.'));
|
||||
}
|
||||
|
||||
///
|
||||
ChannelPage(this._root);
|
||||
|
||||
///
|
||||
ChannelPage.parse(String raw) : _root = parser.parse(raw);
|
||||
ChannelPage.parse(String raw)
|
||||
: super(parser.parse(raw), (root) => _InitialData(root));
|
||||
|
||||
///
|
||||
static Future<ChannelPage> get(YoutubeHttpClient httpClient, String id) {
|
||||
|
@ -85,13 +68,10 @@ class ChannelPage {
|
|||
}
|
||||
}
|
||||
|
||||
class _InitialData {
|
||||
class _InitialData extends InitialData {
|
||||
static final RegExp _subCountExp = RegExp(r'(\d+(?:\.\d+)?)(K|M|\s)');
|
||||
|
||||
// Json parsed map
|
||||
final Map<String, dynamic> root;
|
||||
|
||||
_InitialData(this.root);
|
||||
_InitialData(JsonMap root) : super(root);
|
||||
|
||||
int? get subscribersCount {
|
||||
final renderer = root.get('header')?.get('c4TabbedHeaderRenderer');
|
|
@ -1,8 +1,7 @@
|
|||
import 'dart:convert';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:html/dom.dart';
|
||||
import 'package:html/parser.dart' as parser;
|
||||
import 'package:youtube_explode_dart/src/reverse_engineering/models/youtube_page.dart';
|
||||
|
||||
import '../../channels/channel_video.dart';
|
||||
import '../../exceptions/exceptions.dart';
|
||||
|
@ -10,60 +9,27 @@ import '../../extensions/helpers_extension.dart';
|
|||
import '../../retry.dart';
|
||||
import '../../videos/videos.dart';
|
||||
import '../youtube_http_client.dart';
|
||||
import '../models/initial_data.dart';
|
||||
|
||||
///
|
||||
class ChannelUploadPage {
|
||||
class ChannelUploadPage extends YoutubePage<_InitialData> {
|
||||
///
|
||||
final String channelId;
|
||||
final Document? _root;
|
||||
|
||||
late final _InitialData initialData = _getInitialData();
|
||||
_InitialData? _initialData;
|
||||
late final List<ChannelVideo> uploads = initialData.uploads;
|
||||
|
||||
/// InitialData
|
||||
ChannelUploadPage.id(this.channelId, _InitialData? initialData)
|
||||
: super(null, null, initialData);
|
||||
|
||||
///
|
||||
_InitialData _getInitialData() {
|
||||
if (_initialData != null) {
|
||||
return _initialData!;
|
||||
}
|
||||
final scriptText = _root!
|
||||
.querySelectorAll('script')
|
||||
.map((e) => e.text)
|
||||
.toList(growable: false);
|
||||
|
||||
return scriptText.extractGenericData(
|
||||
(obj) => _InitialData(obj),
|
||||
() => TransientFailureException(
|
||||
'Failed to retrieve initial data from the channel upload page, please report this to the project GitHub page.'));
|
||||
}
|
||||
|
||||
///
|
||||
ChannelUploadPage(this._root, this.channelId, [_InitialData? initialData])
|
||||
: _initialData = initialData;
|
||||
|
||||
///
|
||||
Future<ChannelUploadPage?> nextPage(YoutubeHttpClient httpClient) {
|
||||
Future<ChannelUploadPage?> nextPage(YoutubeHttpClient httpClient) async {
|
||||
if (initialData.token.isEmpty) {
|
||||
return Future.value(null);
|
||||
return null;
|
||||
}
|
||||
|
||||
final url = Uri.parse(
|
||||
'https://www.youtube.com/youtubei/v1/browse?key=AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8');
|
||||
|
||||
final body = {
|
||||
'context': const {
|
||||
'client': {
|
||||
'hl': 'en',
|
||||
'clientName': 'WEB',
|
||||
'clientVersion': '2.20200911.04.00'
|
||||
}
|
||||
},
|
||||
'continuation': initialData.token
|
||||
};
|
||||
return retry(() async {
|
||||
var raw = await httpClient.post(url, body: json.encode(body));
|
||||
return ChannelUploadPage(
|
||||
null, channelId, _InitialData(json.decode(raw.body)));
|
||||
});
|
||||
final data = await httpClient.sendPost('browse', initialData.token);
|
||||
return ChannelUploadPage.id(channelId, _InitialData(data));
|
||||
}
|
||||
|
||||
///
|
||||
|
@ -79,17 +45,13 @@ class ChannelUploadPage {
|
|||
|
||||
///
|
||||
ChannelUploadPage.parse(String raw, this.channelId)
|
||||
: _root = parser.parse(raw);
|
||||
: super(parser.parse(raw), (root) => _InitialData(root));
|
||||
}
|
||||
|
||||
class _InitialData {
|
||||
// Json parsed map
|
||||
final Map<String, dynamic> root;
|
||||
class _InitialData extends InitialData {
|
||||
_InitialData(JsonMap root) : super(root);
|
||||
|
||||
_InitialData(this.root);
|
||||
|
||||
late final Map<String, dynamic>? continuationContext =
|
||||
getContinuationContext();
|
||||
late final JsonMap? continuationContext = getContinuationContext();
|
||||
|
||||
late final String token = continuationContext?.getT<String>('token') ?? '';
|
||||
|
||||
|
@ -103,15 +65,15 @@ class _InitialData {
|
|||
return content.map(_parseContent).whereNotNull().toList();
|
||||
}
|
||||
|
||||
List<Map<String, dynamic>> getContentContext() {
|
||||
List<Map<String, dynamic>>? context;
|
||||
List<JsonMap> getContentContext() {
|
||||
List<JsonMap>? context;
|
||||
if (root.containsKey('contents')) {
|
||||
final render = root
|
||||
.get('contents')
|
||||
?.get('twoColumnBrowseResultsRenderer')
|
||||
?.getList('tabs')
|
||||
?.map((e) => e['tabRenderer'])
|
||||
.cast<Map<String, dynamic>>()
|
||||
.cast<JsonMap>()
|
||||
.firstWhereOrNull((e) => e['selected'] as bool)
|
||||
?.get('content')
|
||||
?.get('sectionListRenderer')
|
||||
|
@ -122,10 +84,8 @@ class _InitialData {
|
|||
?.firstOrNull;
|
||||
|
||||
if (render?.containsKey('gridRenderer') ?? false) {
|
||||
context = render
|
||||
?.get('gridRenderer')
|
||||
?.getList('items')
|
||||
?.cast<Map<String, dynamic>>();
|
||||
context =
|
||||
render?.get('gridRenderer')?.getList('items')?.cast<JsonMap>();
|
||||
} else if (render?.containsKey('messageRenderer') ?? false) {
|
||||
// Workaround for no-videos.
|
||||
context = const [];
|
||||
|
@ -137,7 +97,7 @@ class _InitialData {
|
|||
?.firstOrNull
|
||||
?.get('appendContinuationItemsAction')
|
||||
?.getList('continuationItems')
|
||||
?.cast<Map<String, dynamic>>();
|
||||
?.cast<JsonMap>();
|
||||
}
|
||||
if (context == null) {
|
||||
throw FatalFailureException('Failed to get initial data context.');
|
||||
|
@ -145,14 +105,14 @@ class _InitialData {
|
|||
return context;
|
||||
}
|
||||
|
||||
Map<String, dynamic>? getContinuationContext() {
|
||||
JsonMap? getContinuationContext() {
|
||||
if (root.containsKey('contents')) {
|
||||
return root
|
||||
.get('contents')
|
||||
?.get('twoColumnBrowseResultsRenderer')
|
||||
?.getList('tabs')
|
||||
?.map((e) => e['tabRenderer'])
|
||||
.cast<Map<String, dynamic>>()
|
||||
.cast<JsonMap>()
|
||||
.firstWhereOrNull((e) => e['selected'] as bool)
|
||||
?.get('content')
|
||||
?.get('sectionListRenderer')
|
||||
|
@ -182,7 +142,7 @@ class _InitialData {
|
|||
return null;
|
||||
}
|
||||
|
||||
ChannelVideo? _parseContent(Map<String, dynamic>? content) {
|
||||
ChannelVideo? _parseContent(JsonMap? content) {
|
||||
if (content == null || !content.containsKey('gridVideoRenderer')) {
|
||||
return null;
|
||||
}
|
|
@ -79,9 +79,9 @@ class EmbedPage {
|
|||
}
|
||||
|
||||
/// Used internally
|
||||
class EmbedPlayerConfig implements PlayerConfigBase<Map<String, dynamic>> {
|
||||
class EmbedPlayerConfig implements PlayerConfigBase {
|
||||
@override
|
||||
final Map<String, dynamic> root;
|
||||
final JsonMap root;
|
||||
|
||||
///
|
||||
EmbedPlayerConfig(this.root);
|
|
@ -1,7 +1,9 @@
|
|||
import '../../extensions/helpers_extension.dart';
|
||||
|
||||
/// Base class for PlayerConfig.
|
||||
abstract class PlayerConfigBase<T> {
|
||||
abstract class PlayerConfigBase {
|
||||
/// Root node.
|
||||
final T root;
|
||||
final JsonMap root;
|
||||
|
||||
///
|
||||
PlayerConfigBase(this.root);
|
|
@ -1,95 +1,63 @@
|
|||
import 'dart:convert';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:html/dom.dart';
|
||||
import 'package:html/parser.dart' as parser;
|
||||
import 'package:youtube_explode_dart/src/reverse_engineering/models/youtube_page.dart';
|
||||
|
||||
import '../../../youtube_explode_dart.dart';
|
||||
import '../../extensions/helpers_extension.dart';
|
||||
import '../../retry.dart';
|
||||
import '../youtube_http_client.dart';
|
||||
import '../models/initial_data.dart';
|
||||
|
||||
///
|
||||
class PlaylistPage {
|
||||
class PlaylistPage extends YoutubePage<_InitialData> {
|
||||
///
|
||||
final String playlistId;
|
||||
final Document? root;
|
||||
|
||||
late final _InitialData initialData = getInitialData();
|
||||
_InitialData? _initialData;
|
||||
late final List<_Video> videos = initialData.playlistVideos;
|
||||
|
||||
///
|
||||
_InitialData getInitialData() {
|
||||
if (_initialData != null) {
|
||||
return _initialData!;
|
||||
}
|
||||
late final String? title = initialData.title;
|
||||
|
||||
final scriptText = root!
|
||||
.querySelectorAll('script')
|
||||
.map((e) => e.text)
|
||||
.toList(growable: false);
|
||||
late final String? description = initialData.description;
|
||||
|
||||
return scriptText.extractGenericData(
|
||||
(obj) => _InitialData(obj),
|
||||
() => TransientFailureException(
|
||||
'Failed to retrieve initial data from the search page, please report this to the project GitHub page.'));
|
||||
}
|
||||
late final String? author = initialData.author;
|
||||
|
||||
///
|
||||
PlaylistPage(this.root, this.playlistId, [_InitialData? initialData])
|
||||
: _initialData = initialData;
|
||||
late final int? viewCount = initialData.viewCount;
|
||||
|
||||
/// InitialData
|
||||
PlaylistPage.id(this.playlistId, _InitialData initialData)
|
||||
: super(null, null, initialData);
|
||||
|
||||
///
|
||||
Future<PlaylistPage?> nextPage(YoutubeHttpClient httpClient) async {
|
||||
if (initialData.continuationToken == null) {
|
||||
if (initialData.continuationToken?.isEmpty == null) {
|
||||
return null;
|
||||
}
|
||||
return get(httpClient, playlistId, token: initialData.continuationToken);
|
||||
|
||||
final data =
|
||||
await httpClient.sendPost('browse', initialData.continuationToken!);
|
||||
|
||||
return PlaylistPage.id(playlistId, _InitialData(data));
|
||||
}
|
||||
|
||||
///
|
||||
static Future<PlaylistPage> get(YoutubeHttpClient httpClient, String id,
|
||||
{String? token}) {
|
||||
if (token != null && token.isNotEmpty) {
|
||||
var url =
|
||||
'https://www.youtube.com/youtubei/v1/browse?key=AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8';
|
||||
|
||||
return retry(() async {
|
||||
var body = {
|
||||
'context': const {
|
||||
'client': {
|
||||
'hl': 'en',
|
||||
'clientName': 'WEB',
|
||||
'clientVersion': '2.20200911.04.00'
|
||||
}
|
||||
},
|
||||
'continuation': token
|
||||
};
|
||||
|
||||
var raw =
|
||||
await httpClient.post(Uri.parse(url), body: json.encode(body));
|
||||
return PlaylistPage(null, id, _InitialData(json.decode(raw.body)));
|
||||
});
|
||||
// Ask for next page,
|
||||
|
||||
}
|
||||
static Future<PlaylistPage> get(
|
||||
YoutubeHttpClient httpClient,
|
||||
String id,
|
||||
) async {
|
||||
var url = 'https://www.youtube.com/playlist?list=$id&hl=en&persist_hl=1';
|
||||
return retry(() async {
|
||||
var raw = await httpClient.getString(url);
|
||||
return PlaylistPage.parse(raw, id);
|
||||
});
|
||||
// ask for next page
|
||||
}
|
||||
|
||||
///
|
||||
PlaylistPage.parse(String raw, this.playlistId) : root = parser.parse(raw);
|
||||
PlaylistPage.parse(String raw, this.playlistId)
|
||||
: super(parser.parse(raw), (root) => _InitialData(root));
|
||||
}
|
||||
|
||||
class _InitialData {
|
||||
// Json parsed map
|
||||
final Map<String, dynamic> root;
|
||||
|
||||
_InitialData(this.root);
|
||||
class _InitialData extends InitialData {
|
||||
_InitialData(JsonMap root) : super(root);
|
||||
|
||||
late final String? title = root
|
||||
.get('metadata')
|
||||
|
@ -132,7 +100,7 @@ class _InitialData {
|
|||
?.get('continuationCommand')
|
||||
?.getT<String>('token');
|
||||
|
||||
List<Map<String, dynamic>>? get playlistVideosContent =>
|
||||
List<JsonMap>? get playlistVideosContent =>
|
||||
root
|
||||
.get('contents')
|
||||
?.get('twoColumnBrowseResultsRenderer')
|
||||
|
@ -154,7 +122,7 @@ class _InitialData {
|
|||
?.get('appendContinuationItemsAction')
|
||||
?.getList('continuationItems');
|
||||
|
||||
late final List<Map<String, dynamic>>? videosContent = root
|
||||
late final List<JsonMap>? videosContent = root
|
||||
.get('contents')
|
||||
?.get('twoColumnSearchResultsRenderer')
|
||||
?.get('primaryContents')
|
||||
|
@ -173,19 +141,19 @@ class _InitialData {
|
|||
.toList() ??
|
||||
const [];
|
||||
|
||||
List<_Video> get videos =>
|
||||
/* List<_Video> get videos =>
|
||||
videosContent?.firstOrNull
|
||||
?.get('itemSectionRenderer')
|
||||
?.getList('contents')
|
||||
?.where((e) => e['videoRenderer'] != null)
|
||||
.map((e) => _Video(e))
|
||||
.toList() ??
|
||||
const [];
|
||||
const [];*/
|
||||
}
|
||||
|
||||
class _Video {
|
||||
// Json parsed map
|
||||
final Map<String, dynamic> root;
|
||||
final JsonMap root;
|
||||
|
||||
_Video(this.root);
|
||||
|
|
@ -1,8 +1,6 @@
|
|||
import 'dart:convert';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:html/dom.dart';
|
||||
import 'package:html/parser.dart' as parser;
|
||||
import 'package:youtube_explode_dart/src/reverse_engineering/models/youtube_page.dart';
|
||||
import 'package:youtube_explode_dart/src/search/search_channel.dart';
|
||||
|
||||
import '../../../youtube_explode_dart.dart';
|
||||
|
@ -14,72 +12,32 @@ import '../../search/search_filter.dart';
|
|||
import '../../search/search_video.dart';
|
||||
import '../../videos/videos.dart';
|
||||
import '../youtube_http_client.dart';
|
||||
import '../models/initial_data.dart';
|
||||
|
||||
///
|
||||
class SearchPage {
|
||||
class SearchPage extends YoutubePage<_InitialData> {
|
||||
///
|
||||
final String queryString;
|
||||
final Document? root;
|
||||
|
||||
late final _InitialData initialData = getInitialData();
|
||||
_InitialData? _initialData;
|
||||
|
||||
///
|
||||
_InitialData getInitialData() {
|
||||
if (_initialData != null) {
|
||||
return _initialData!;
|
||||
}
|
||||
|
||||
final scriptText = root!
|
||||
.querySelectorAll('script')
|
||||
.map((e) => e.text)
|
||||
.toList(growable: false);
|
||||
return scriptText.extractGenericData(
|
||||
(obj) => _InitialData(obj),
|
||||
() => TransientFailureException(
|
||||
'Failed to retrieve initial data from the search page, please report this to the project GitHub page.'));
|
||||
}
|
||||
|
||||
///
|
||||
SearchPage(this.root, this.queryString, [_InitialData? initialData])
|
||||
: _initialData = initialData;
|
||||
/// InitialData
|
||||
SearchPage.id(this.queryString, _InitialData initialData)
|
||||
: super(null, null, initialData);
|
||||
|
||||
Future<SearchPage?> nextPage(YoutubeHttpClient httpClient) async {
|
||||
if (initialData.continuationToken == '' ||
|
||||
if (initialData.continuationToken?.isEmpty == null ||
|
||||
initialData.estimatedResults == 0) {
|
||||
return null;
|
||||
}
|
||||
return get(httpClient, queryString, token: initialData.continuationToken);
|
||||
|
||||
var data =
|
||||
await httpClient.sendPost('search', initialData.continuationToken!);
|
||||
return SearchPage.id(queryString, _InitialData(data));
|
||||
}
|
||||
|
||||
///
|
||||
static Future<SearchPage> get(
|
||||
YoutubeHttpClient httpClient, String queryString,
|
||||
{String? token, SearchFilter filter = const SearchFilter('')}) {
|
||||
if (token != null) {
|
||||
var url =
|
||||
'https://www.youtube.com/youtubei/v1/search?key=AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8';
|
||||
|
||||
return retry(() async {
|
||||
var body = {
|
||||
'context': const {
|
||||
'client': {
|
||||
'hl': 'en',
|
||||
'clientName': 'WEB',
|
||||
'clientVersion': '2.20200911.04.00'
|
||||
}
|
||||
},
|
||||
'continuation': token
|
||||
};
|
||||
|
||||
var raw =
|
||||
await httpClient.post(Uri.parse(url), body: json.encode(body));
|
||||
return SearchPage(
|
||||
null, queryString, _InitialData(json.decode(raw.body)));
|
||||
});
|
||||
// Ask for next page,
|
||||
|
||||
}
|
||||
{SearchFilter filter = const SearchFilter('')}) {
|
||||
var url =
|
||||
'https://www.youtube.com/results?search_query=${Uri.encodeQueryComponent(queryString)}&sp=${filter.value}';
|
||||
return retry(() async {
|
||||
|
@ -90,16 +48,14 @@ class SearchPage {
|
|||
}
|
||||
|
||||
///
|
||||
SearchPage.parse(String raw, this.queryString) : root = parser.parse(raw);
|
||||
SearchPage.parse(String raw, this.queryString)
|
||||
: super(parser.parse(raw), (root) => _InitialData(root));
|
||||
}
|
||||
|
||||
class _InitialData {
|
||||
// Json parsed map
|
||||
final Map<String, dynamic> root;
|
||||
class _InitialData extends InitialData {
|
||||
_InitialData(JsonMap root) : super(root);
|
||||
|
||||
_InitialData(this.root);
|
||||
|
||||
List<Map<String, dynamic>>? getContentContext() {
|
||||
List<JsonMap>? getContentContext() {
|
||||
if (root['contents'] != null) {
|
||||
return root
|
||||
.get('contents')
|
||||
|
@ -196,7 +152,7 @@ class _InitialData {
|
|||
late final int estimatedResults =
|
||||
int.parse(root.getT<String>('estimatedResults') ?? '0');
|
||||
|
||||
BaseSearchContent? _parseContent(Map<String, dynamic>? content) {
|
||||
BaseSearchContent? _parseContent(JsonMap? content) {
|
||||
if (content == null) {
|
||||
return null;
|
||||
}
|
|
@ -1,17 +1,19 @@
|
|||
import 'package:collection/collection.dart';
|
||||
import 'package:html/dom.dart';
|
||||
import 'package:html/parser.dart' as parser;
|
||||
import 'package:youtube_explode_dart/src/reverse_engineering/models/youtube_page.dart';
|
||||
|
||||
import '../../../youtube_explode_dart.dart';
|
||||
import '../../extensions/helpers_extension.dart';
|
||||
import '../../retry.dart';
|
||||
import '../../videos/video_id.dart';
|
||||
import '../player/player_response.dart';
|
||||
import '../youtube_http_client.dart';
|
||||
import 'player_config_base.dart';
|
||||
import 'player_response.dart';
|
||||
import '../models/initial_data.dart';
|
||||
|
||||
///
|
||||
class WatchPage {
|
||||
class WatchPage extends YoutubePage<_InitialData> {
|
||||
static final RegExp _videoLikeExp =
|
||||
RegExp(r'"label"\s*:\s*"([\d,\.]+) likes"');
|
||||
static final RegExp _videoDislikeExp =
|
||||
|
@ -24,6 +26,7 @@ class WatchPage {
|
|||
|
||||
static final _xsfrTokenExp = RegExp(r'"XSRF_TOKEN"\s*:\s*"(.+?)"');
|
||||
|
||||
@override
|
||||
final Document root;
|
||||
|
||||
///
|
||||
|
@ -32,8 +35,6 @@ class WatchPage {
|
|||
///
|
||||
final String ysc;
|
||||
|
||||
_InitialData? _initialData;
|
||||
|
||||
///
|
||||
String? get sourceUrl {
|
||||
var url = root
|
||||
|
@ -47,24 +48,6 @@ class WatchPage {
|
|||
return 'https://youtube.com$url';
|
||||
}
|
||||
|
||||
late final _InitialData initialData = getInitialData();
|
||||
|
||||
///
|
||||
_InitialData getInitialData() {
|
||||
if (_initialData != null) {
|
||||
return _initialData!;
|
||||
}
|
||||
|
||||
final scriptText = root
|
||||
.querySelectorAll('script')
|
||||
.map((e) => e.text)
|
||||
.toList(growable: false);
|
||||
return scriptText.extractGenericData(
|
||||
(obj) => _InitialData(obj),
|
||||
() => TransientFailureException(
|
||||
'Failed to retrieve initial data from the watch page, please report this to the project GitHub page.'));
|
||||
}
|
||||
|
||||
late final String xsfrToken = getXsfrToken()!.replaceAll(r'\u003d', '=');
|
||||
|
||||
///
|
||||
|
@ -142,12 +125,10 @@ class WatchPage {
|
|||
return PlayerResponse(val);
|
||||
}
|
||||
|
||||
///
|
||||
WatchPage(this.root, this.visitorInfoLive, this.ysc);
|
||||
|
||||
///
|
||||
WatchPage.parse(String raw, this.visitorInfoLive, this.ysc)
|
||||
: root = parser.parse(raw);
|
||||
: root = parser.parse(raw),
|
||||
super(parser.parse(raw), (root) => _InitialData(root));
|
||||
|
||||
///
|
||||
static Future<WatchPage> get(YoutubeHttpClient httpClient, String videoId) {
|
||||
|
@ -173,9 +154,9 @@ class WatchPage {
|
|||
}
|
||||
|
||||
/// Used internally
|
||||
class WatchPlayerConfig implements PlayerConfigBase<Map<String, dynamic>> {
|
||||
class WatchPlayerConfig implements PlayerConfigBase {
|
||||
@override
|
||||
final Map<String, dynamic> root;
|
||||
final JsonMap root;
|
||||
|
||||
///
|
||||
WatchPlayerConfig(this.root);
|
||||
|
@ -189,13 +170,10 @@ class WatchPlayerConfig implements PlayerConfigBase<Map<String, dynamic>> {
|
|||
PlayerResponse.parse(root.get('args')!.getT<String>('playerResponse')!);
|
||||
}
|
||||
|
||||
class _InitialData {
|
||||
// Json parsed map
|
||||
final Map<String, dynamic> root;
|
||||
class _InitialData extends InitialData {
|
||||
_InitialData(JsonMap root) : super(root);
|
||||
|
||||
_InitialData(this.root);
|
||||
|
||||
Map<String, dynamic>? getContinuationContext() {
|
||||
JsonMap? getContinuationContext() {
|
||||
if (root['contents'] != null) {
|
||||
return root
|
||||
.get('contents')
|
|
@ -4,12 +4,12 @@ import 'package:collection/collection.dart';
|
|||
import 'package:http_parser/http_parser.dart';
|
||||
|
||||
import '../../extensions/helpers_extension.dart';
|
||||
import 'stream_info_provider.dart';
|
||||
import '../models/stream_info_provider.dart';
|
||||
|
||||
///
|
||||
class PlayerResponse {
|
||||
// Json parsed map
|
||||
Map<String, dynamic> root;
|
||||
JsonMap root;
|
||||
|
||||
///
|
||||
late final String playabilityStatus =
|
||||
|
@ -139,7 +139,7 @@ class PlayerResponse {
|
|||
///
|
||||
class ClosedCaptionTrack {
|
||||
// Json parsed class
|
||||
final Map<String, dynamic> root;
|
||||
final JsonMap root;
|
||||
|
||||
///
|
||||
String get url => root.getT<String>('baseUrl')!;
|
||||
|
@ -163,7 +163,7 @@ class _StreamInfo extends StreamInfoProvider {
|
|||
static final _contentLenExp = RegExp(r'[\?&]clen=(\d+)');
|
||||
|
||||
/// Json parsed map
|
||||
final Map<String, dynamic> root;
|
||||
final JsonMap root;
|
||||
|
||||
@override
|
||||
late final int? bitrate = root.getT<int>('bitrate');
|
|
@ -1,12 +0,0 @@
|
|||
library _youtube_explode.responses;
|
||||
|
||||
export 'channel_page.dart';
|
||||
export 'closed_caption_track_response.dart';
|
||||
export 'dash_manifest.dart';
|
||||
export 'embed_page.dart';
|
||||
export 'player_response.dart';
|
||||
export 'player_source.dart';
|
||||
export 'playlist_page.dart';
|
||||
export 'stream_info_provider.dart';
|
||||
export 'video_info_response.dart';
|
||||
export 'watch_page.dart';
|
|
@ -4,8 +4,8 @@ import '../../exceptions/exceptions.dart';
|
|||
import '../../extensions/helpers_extension.dart';
|
||||
import '../../retry.dart';
|
||||
import '../youtube_http_client.dart';
|
||||
import 'player_response.dart';
|
||||
import 'stream_info_provider.dart';
|
||||
import '../player/player_response.dart';
|
||||
import '../models/stream_info_provider.dart';
|
||||
|
||||
///
|
||||
class VideoInfoResponse {
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:youtube_explode_dart/src/retry.dart';
|
||||
|
||||
import '../exceptions/exceptions.dart';
|
||||
import '../extensions/helpers_extension.dart';
|
||||
import '../videos/streams/streams.dart';
|
||||
|
||||
/// HttpClient wrapper for YouTube
|
||||
|
@ -143,6 +145,30 @@ class YoutubeHttpClient extends http.BaseClient {
|
|||
return int.tryParse(response.headers['content-length'] ?? '');
|
||||
}
|
||||
|
||||
/// Sends a call to the youtube api endpoint.
|
||||
Future<JsonMap> sendPost(String action, String token) async {
|
||||
assert(action == 'next' || action == 'browse' || action == 'search');
|
||||
|
||||
final body = {
|
||||
'context': const {
|
||||
'client': {
|
||||
'hl': 'en',
|
||||
'clientName': 'WEB',
|
||||
'clientVersion': '2.20200911.04.00'
|
||||
}
|
||||
},
|
||||
'continuation': token
|
||||
};
|
||||
|
||||
final url = Uri.parse(
|
||||
'https://www.youtube.com/youtubei/v1/$action?key=AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8');
|
||||
|
||||
return retry<JsonMap>(() async {
|
||||
final raw = await post(url, body: json.encode(body));
|
||||
return json.decode(raw.body);
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void close() => _httpClient.close();
|
||||
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import 'package:equatable/equatable.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
import '../channels/channel_id.dart';
|
||||
|
|
|
@ -3,7 +3,7 @@ import 'dart:convert';
|
|||
import '../../youtube_explode_dart.dart';
|
||||
import '../extensions/helpers_extension.dart';
|
||||
import '../retry.dart';
|
||||
import '../reverse_engineering/responses/search_page.dart';
|
||||
import '../reverse_engineering/pages/search_page.dart';
|
||||
import '../reverse_engineering/youtube_http_client.dart';
|
||||
import 'base_search_content.dart';
|
||||
import 'search_filter.dart';
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:youtube_explode_dart/src/reverse_engineering/responses/search_page.dart';
|
||||
import 'package:youtube_explode_dart/src/reverse_engineering/pages/search_page.dart';
|
||||
|
||||
import '../../youtube_explode_dart.dart';
|
||||
import '../extensions/helpers_extension.dart';
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import '../reverse_engineering/responses/search_page.dart';
|
||||
import '../reverse_engineering/pages/search_page.dart';
|
||||
import '../reverse_engineering/youtube_http_client.dart';
|
||||
import 'related_query.dart';
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import '../../extensions/helpers_extension.dart';
|
||||
import '../../reverse_engineering/responses/responses.dart'
|
||||
hide ClosedCaption, ClosedCaptionPart, ClosedCaptionTrack;
|
||||
import '../../reverse_engineering/responses/closed_caption_track_response.dart'
|
||||
show ClosedCaptionTrackResponse;
|
||||
import '../../reverse_engineering/responses/video_info_response.dart';
|
||||
import '../../reverse_engineering/youtube_http_client.dart';
|
||||
import '../videos.dart';
|
||||
import 'closed_caption.dart';
|
||||
|
|
|
@ -1,30 +1,33 @@
|
|||
import 'package:equatable/equatable.dart';
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
import '../../extensions/helpers_extension.dart';
|
||||
import 'closed_caption_format.dart';
|
||||
import 'language.dart';
|
||||
|
||||
part 'closed_caption_track_info.freezed.dart';
|
||||
|
||||
part 'closed_caption_track_info.g.dart';
|
||||
|
||||
/// Metadata associated with a certain [ClosedCaptionTrack]
|
||||
@JsonSerializable()
|
||||
class ClosedCaptionTrackInfo extends Equatable {
|
||||
/// Manifest URL of the associated track.
|
||||
final Uri url;
|
||||
|
||||
/// Language of the associated track.
|
||||
final Language language;
|
||||
|
||||
/// Whether the associated track was automatically generated.
|
||||
final bool isAutoGenerated;
|
||||
|
||||
/// Track format
|
||||
final ClosedCaptionFormat format;
|
||||
|
||||
@freezed
|
||||
class ClosedCaptionTrackInfo with _$ClosedCaptionTrackInfo {
|
||||
/// Initializes an instance of [ClosedCaptionTrackInfo]
|
||||
const ClosedCaptionTrackInfo(this.url, this.language,
|
||||
{this.isAutoGenerated = false, required this.format});
|
||||
const factory ClosedCaptionTrackInfo(
|
||||
|
||||
/// Manifest URL of the associated track.
|
||||
Uri url,
|
||||
|
||||
/// Language of the associated track.
|
||||
Language language,
|
||||
{
|
||||
|
||||
/// Whether the associated track was automatically generated.
|
||||
@Default(false) bool isAutoGenerated,
|
||||
|
||||
/// Track format
|
||||
required ClosedCaptionFormat format}) = _ClosedCaptionTrackInfo;
|
||||
|
||||
const ClosedCaptionTrackInfo._();
|
||||
|
||||
/// Returns this auto-translated to another language.
|
||||
/// Keeping the same format.
|
||||
|
@ -37,13 +40,7 @@ class ClosedCaptionTrackInfo extends Equatable {
|
|||
@override
|
||||
String toString() => 'CC Track ($language)';
|
||||
|
||||
@override
|
||||
List<Object> get props => [url, language, isAutoGenerated];
|
||||
|
||||
///
|
||||
factory ClosedCaptionTrackInfo.fromJson(Map<String, dynamic> json) =>
|
||||
_$ClosedCaptionTrackInfoFromJson(json);
|
||||
|
||||
///
|
||||
Map<String, dynamic> toJson() => _$ClosedCaptionTrackInfoToJson(this);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,269 @@
|
|||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target
|
||||
|
||||
part of 'closed_caption_track_info.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// FreezedGenerator
|
||||
// **************************************************************************
|
||||
|
||||
T _$identity<T>(T value) => value;
|
||||
|
||||
final _privateConstructorUsedError = UnsupportedError(
|
||||
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more informations: https://github.com/rrousselGit/freezed#custom-getters-and-methods');
|
||||
|
||||
ClosedCaptionTrackInfo _$ClosedCaptionTrackInfoFromJson(
|
||||
Map<String, dynamic> json) {
|
||||
return _ClosedCaptionTrackInfo.fromJson(json);
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$ClosedCaptionTrackInfoTearOff {
|
||||
const _$ClosedCaptionTrackInfoTearOff();
|
||||
|
||||
_ClosedCaptionTrackInfo call(Uri url, Language language,
|
||||
{bool isAutoGenerated = false, required ClosedCaptionFormat format}) {
|
||||
return _ClosedCaptionTrackInfo(
|
||||
url,
|
||||
language,
|
||||
isAutoGenerated: isAutoGenerated,
|
||||
format: format,
|
||||
);
|
||||
}
|
||||
|
||||
ClosedCaptionTrackInfo fromJson(Map<String, Object> json) {
|
||||
return ClosedCaptionTrackInfo.fromJson(json);
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
const $ClosedCaptionTrackInfo = _$ClosedCaptionTrackInfoTearOff();
|
||||
|
||||
/// @nodoc
|
||||
mixin _$ClosedCaptionTrackInfo {
|
||||
/// Manifest URL of the associated track.
|
||||
Uri get url => throw _privateConstructorUsedError;
|
||||
|
||||
/// Language of the associated track.
|
||||
Language get language => throw _privateConstructorUsedError;
|
||||
|
||||
/// Whether the associated track was automatically generated.
|
||||
bool get isAutoGenerated => throw _privateConstructorUsedError;
|
||||
|
||||
/// Track format
|
||||
ClosedCaptionFormat get format => throw _privateConstructorUsedError;
|
||||
|
||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||
@JsonKey(ignore: true)
|
||||
$ClosedCaptionTrackInfoCopyWith<ClosedCaptionTrackInfo> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $ClosedCaptionTrackInfoCopyWith<$Res> {
|
||||
factory $ClosedCaptionTrackInfoCopyWith(ClosedCaptionTrackInfo value,
|
||||
$Res Function(ClosedCaptionTrackInfo) then) =
|
||||
_$ClosedCaptionTrackInfoCopyWithImpl<$Res>;
|
||||
$Res call(
|
||||
{Uri url,
|
||||
Language language,
|
||||
bool isAutoGenerated,
|
||||
ClosedCaptionFormat format});
|
||||
|
||||
$LanguageCopyWith<$Res> get language;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$ClosedCaptionTrackInfoCopyWithImpl<$Res>
|
||||
implements $ClosedCaptionTrackInfoCopyWith<$Res> {
|
||||
_$ClosedCaptionTrackInfoCopyWithImpl(this._value, this._then);
|
||||
|
||||
final ClosedCaptionTrackInfo _value;
|
||||
// ignore: unused_field
|
||||
final $Res Function(ClosedCaptionTrackInfo) _then;
|
||||
|
||||
@override
|
||||
$Res call({
|
||||
Object? url = freezed,
|
||||
Object? language = freezed,
|
||||
Object? isAutoGenerated = freezed,
|
||||
Object? format = freezed,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
url: url == freezed
|
||||
? _value.url
|
||||
: url // ignore: cast_nullable_to_non_nullable
|
||||
as Uri,
|
||||
language: language == freezed
|
||||
? _value.language
|
||||
: language // ignore: cast_nullable_to_non_nullable
|
||||
as Language,
|
||||
isAutoGenerated: isAutoGenerated == freezed
|
||||
? _value.isAutoGenerated
|
||||
: isAutoGenerated // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
format: format == freezed
|
||||
? _value.format
|
||||
: format // ignore: cast_nullable_to_non_nullable
|
||||
as ClosedCaptionFormat,
|
||||
));
|
||||
}
|
||||
|
||||
@override
|
||||
$LanguageCopyWith<$Res> get language {
|
||||
return $LanguageCopyWith<$Res>(_value.language, (value) {
|
||||
return _then(_value.copyWith(language: value));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$ClosedCaptionTrackInfoCopyWith<$Res>
|
||||
implements $ClosedCaptionTrackInfoCopyWith<$Res> {
|
||||
factory _$ClosedCaptionTrackInfoCopyWith(_ClosedCaptionTrackInfo value,
|
||||
$Res Function(_ClosedCaptionTrackInfo) then) =
|
||||
__$ClosedCaptionTrackInfoCopyWithImpl<$Res>;
|
||||
@override
|
||||
$Res call(
|
||||
{Uri url,
|
||||
Language language,
|
||||
bool isAutoGenerated,
|
||||
ClosedCaptionFormat format});
|
||||
|
||||
@override
|
||||
$LanguageCopyWith<$Res> get language;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$ClosedCaptionTrackInfoCopyWithImpl<$Res>
|
||||
extends _$ClosedCaptionTrackInfoCopyWithImpl<$Res>
|
||||
implements _$ClosedCaptionTrackInfoCopyWith<$Res> {
|
||||
__$ClosedCaptionTrackInfoCopyWithImpl(_ClosedCaptionTrackInfo _value,
|
||||
$Res Function(_ClosedCaptionTrackInfo) _then)
|
||||
: super(_value, (v) => _then(v as _ClosedCaptionTrackInfo));
|
||||
|
||||
@override
|
||||
_ClosedCaptionTrackInfo get _value => super._value as _ClosedCaptionTrackInfo;
|
||||
|
||||
@override
|
||||
$Res call({
|
||||
Object? url = freezed,
|
||||
Object? language = freezed,
|
||||
Object? isAutoGenerated = freezed,
|
||||
Object? format = freezed,
|
||||
}) {
|
||||
return _then(_ClosedCaptionTrackInfo(
|
||||
url == freezed
|
||||
? _value.url
|
||||
: url // ignore: cast_nullable_to_non_nullable
|
||||
as Uri,
|
||||
language == freezed
|
||||
? _value.language
|
||||
: language // ignore: cast_nullable_to_non_nullable
|
||||
as Language,
|
||||
isAutoGenerated: isAutoGenerated == freezed
|
||||
? _value.isAutoGenerated
|
||||
: isAutoGenerated // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
format: format == freezed
|
||||
? _value.format
|
||||
: format // ignore: cast_nullable_to_non_nullable
|
||||
as ClosedCaptionFormat,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@JsonSerializable()
|
||||
class _$_ClosedCaptionTrackInfo extends _ClosedCaptionTrackInfo {
|
||||
const _$_ClosedCaptionTrackInfo(this.url, this.language,
|
||||
{this.isAutoGenerated = false, required this.format})
|
||||
: super._();
|
||||
|
||||
factory _$_ClosedCaptionTrackInfo.fromJson(Map<String, dynamic> json) =>
|
||||
_$$_ClosedCaptionTrackInfoFromJson(json);
|
||||
|
||||
@override
|
||||
|
||||
/// Manifest URL of the associated track.
|
||||
final Uri url;
|
||||
@override
|
||||
|
||||
/// Language of the associated track.
|
||||
final Language language;
|
||||
@JsonKey(defaultValue: false)
|
||||
@override
|
||||
|
||||
/// Whether the associated track was automatically generated.
|
||||
final bool isAutoGenerated;
|
||||
@override
|
||||
|
||||
/// Track format
|
||||
final ClosedCaptionFormat format;
|
||||
|
||||
@override
|
||||
bool operator ==(dynamic other) {
|
||||
return identical(this, other) ||
|
||||
(other is _ClosedCaptionTrackInfo &&
|
||||
(identical(other.url, url) ||
|
||||
const DeepCollectionEquality().equals(other.url, url)) &&
|
||||
(identical(other.language, language) ||
|
||||
const DeepCollectionEquality()
|
||||
.equals(other.language, language)) &&
|
||||
(identical(other.isAutoGenerated, isAutoGenerated) ||
|
||||
const DeepCollectionEquality()
|
||||
.equals(other.isAutoGenerated, isAutoGenerated)) &&
|
||||
(identical(other.format, format) ||
|
||||
const DeepCollectionEquality().equals(other.format, format)));
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
runtimeType.hashCode ^
|
||||
const DeepCollectionEquality().hash(url) ^
|
||||
const DeepCollectionEquality().hash(language) ^
|
||||
const DeepCollectionEquality().hash(isAutoGenerated) ^
|
||||
const DeepCollectionEquality().hash(format);
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
_$ClosedCaptionTrackInfoCopyWith<_ClosedCaptionTrackInfo> get copyWith =>
|
||||
__$ClosedCaptionTrackInfoCopyWithImpl<_ClosedCaptionTrackInfo>(
|
||||
this, _$identity);
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return _$$_ClosedCaptionTrackInfoToJson(this);
|
||||
}
|
||||
}
|
||||
|
||||
abstract class _ClosedCaptionTrackInfo extends ClosedCaptionTrackInfo {
|
||||
const factory _ClosedCaptionTrackInfo(Uri url, Language language,
|
||||
{bool isAutoGenerated,
|
||||
required ClosedCaptionFormat format}) = _$_ClosedCaptionTrackInfo;
|
||||
const _ClosedCaptionTrackInfo._() : super._();
|
||||
|
||||
factory _ClosedCaptionTrackInfo.fromJson(Map<String, dynamic> json) =
|
||||
_$_ClosedCaptionTrackInfo.fromJson;
|
||||
|
||||
@override
|
||||
|
||||
/// Manifest URL of the associated track.
|
||||
Uri get url => throw _privateConstructorUsedError;
|
||||
@override
|
||||
|
||||
/// Language of the associated track.
|
||||
Language get language => throw _privateConstructorUsedError;
|
||||
@override
|
||||
|
||||
/// Whether the associated track was automatically generated.
|
||||
bool get isAutoGenerated => throw _privateConstructorUsedError;
|
||||
@override
|
||||
|
||||
/// Track format
|
||||
ClosedCaptionFormat get format => throw _privateConstructorUsedError;
|
||||
@override
|
||||
@JsonKey(ignore: true)
|
||||
_$ClosedCaptionTrackInfoCopyWith<_ClosedCaptionTrackInfo> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
|
@ -6,9 +6,9 @@ part of 'closed_caption_track_info.dart';
|
|||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
ClosedCaptionTrackInfo _$ClosedCaptionTrackInfoFromJson(
|
||||
_$_ClosedCaptionTrackInfo _$$_ClosedCaptionTrackInfoFromJson(
|
||||
Map<String, dynamic> json) =>
|
||||
ClosedCaptionTrackInfo(
|
||||
_$_ClosedCaptionTrackInfo(
|
||||
Uri.parse(json['url'] as String),
|
||||
Language.fromJson(json['language'] as Map<String, dynamic>),
|
||||
isAutoGenerated: json['isAutoGenerated'] as bool? ?? false,
|
||||
|
@ -16,8 +16,8 @@ ClosedCaptionTrackInfo _$ClosedCaptionTrackInfoFromJson(
|
|||
ClosedCaptionFormat.fromJson(json['format'] as Map<String, dynamic>),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$ClosedCaptionTrackInfoToJson(
|
||||
ClosedCaptionTrackInfo instance) =>
|
||||
Map<String, dynamic> _$$_ClosedCaptionTrackInfoToJson(
|
||||
_$_ClosedCaptionTrackInfo instance) =>
|
||||
<String, dynamic>{
|
||||
'url': instance.url.toString(),
|
||||
'language': instance.language,
|
||||
|
|
|
@ -1,22 +1,22 @@
|
|||
import 'package:equatable/equatable.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
part 'language.g.dart';
|
||||
part 'language.freezed.dart';
|
||||
|
||||
/// Language information.
|
||||
@JsonSerializable()
|
||||
class Language extends Equatable {
|
||||
/// ISO 639-1 code of this language.
|
||||
final String code;
|
||||
|
||||
/// Full English name of this language.
|
||||
final String name;
|
||||
|
||||
@freezed
|
||||
class Language with _$Language {
|
||||
/// Initializes an instance of [Language]
|
||||
const Language(this.code, this.name);
|
||||
const factory Language(
|
||||
|
||||
@override
|
||||
List<Object> get props => [code, name];
|
||||
/// ISO 639-1 code of this language.
|
||||
String code,
|
||||
|
||||
/// Full English name of this language.
|
||||
String name) = _Language;
|
||||
|
||||
const Language._();
|
||||
|
||||
@override
|
||||
String toString() => 'Language: $name';
|
||||
|
@ -24,7 +24,4 @@ class Language extends Equatable {
|
|||
///
|
||||
factory Language.fromJson(Map<String, dynamic> json) =>
|
||||
_$LanguageFromJson(json);
|
||||
|
||||
///
|
||||
Map<String, dynamic> toJson() => _$LanguageToJson(this);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,182 @@
|
|||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target
|
||||
|
||||
part of 'language.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// FreezedGenerator
|
||||
// **************************************************************************
|
||||
|
||||
T _$identity<T>(T value) => value;
|
||||
|
||||
final _privateConstructorUsedError = UnsupportedError(
|
||||
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more informations: https://github.com/rrousselGit/freezed#custom-getters-and-methods');
|
||||
|
||||
Language _$LanguageFromJson(Map<String, dynamic> json) {
|
||||
return _Language.fromJson(json);
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$LanguageTearOff {
|
||||
const _$LanguageTearOff();
|
||||
|
||||
_Language call(String code, String name) {
|
||||
return _Language(
|
||||
code,
|
||||
name,
|
||||
);
|
||||
}
|
||||
|
||||
Language fromJson(Map<String, Object> json) {
|
||||
return Language.fromJson(json);
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
const $Language = _$LanguageTearOff();
|
||||
|
||||
/// @nodoc
|
||||
mixin _$Language {
|
||||
/// ISO 639-1 code of this language.
|
||||
String get code => throw _privateConstructorUsedError;
|
||||
|
||||
/// Full English name of this language.
|
||||
String get name => throw _privateConstructorUsedError;
|
||||
|
||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||
@JsonKey(ignore: true)
|
||||
$LanguageCopyWith<Language> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $LanguageCopyWith<$Res> {
|
||||
factory $LanguageCopyWith(Language value, $Res Function(Language) then) =
|
||||
_$LanguageCopyWithImpl<$Res>;
|
||||
$Res call({String code, String name});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$LanguageCopyWithImpl<$Res> implements $LanguageCopyWith<$Res> {
|
||||
_$LanguageCopyWithImpl(this._value, this._then);
|
||||
|
||||
final Language _value;
|
||||
// ignore: unused_field
|
||||
final $Res Function(Language) _then;
|
||||
|
||||
@override
|
||||
$Res call({
|
||||
Object? code = freezed,
|
||||
Object? name = freezed,
|
||||
}) {
|
||||
return _then(_value.copyWith(
|
||||
code: code == freezed
|
||||
? _value.code
|
||||
: code // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
name: name == freezed
|
||||
? _value.name
|
||||
: name // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$LanguageCopyWith<$Res> implements $LanguageCopyWith<$Res> {
|
||||
factory _$LanguageCopyWith(_Language value, $Res Function(_Language) then) =
|
||||
__$LanguageCopyWithImpl<$Res>;
|
||||
@override
|
||||
$Res call({String code, String name});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$LanguageCopyWithImpl<$Res> extends _$LanguageCopyWithImpl<$Res>
|
||||
implements _$LanguageCopyWith<$Res> {
|
||||
__$LanguageCopyWithImpl(_Language _value, $Res Function(_Language) _then)
|
||||
: super(_value, (v) => _then(v as _Language));
|
||||
|
||||
@override
|
||||
_Language get _value => super._value as _Language;
|
||||
|
||||
@override
|
||||
$Res call({
|
||||
Object? code = freezed,
|
||||
Object? name = freezed,
|
||||
}) {
|
||||
return _then(_Language(
|
||||
code == freezed
|
||||
? _value.code
|
||||
: code // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
name == freezed
|
||||
? _value.name
|
||||
: name // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@JsonSerializable()
|
||||
class _$_Language extends _Language {
|
||||
const _$_Language(this.code, this.name) : super._();
|
||||
|
||||
factory _$_Language.fromJson(Map<String, dynamic> json) =>
|
||||
_$$_LanguageFromJson(json);
|
||||
|
||||
@override
|
||||
|
||||
/// ISO 639-1 code of this language.
|
||||
final String code;
|
||||
@override
|
||||
|
||||
/// Full English name of this language.
|
||||
final String name;
|
||||
|
||||
@override
|
||||
bool operator ==(dynamic other) {
|
||||
return identical(this, other) ||
|
||||
(other is _Language &&
|
||||
(identical(other.code, code) ||
|
||||
const DeepCollectionEquality().equals(other.code, code)) &&
|
||||
(identical(other.name, name) ||
|
||||
const DeepCollectionEquality().equals(other.name, name)));
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
runtimeType.hashCode ^
|
||||
const DeepCollectionEquality().hash(code) ^
|
||||
const DeepCollectionEquality().hash(name);
|
||||
|
||||
@JsonKey(ignore: true)
|
||||
@override
|
||||
_$LanguageCopyWith<_Language> get copyWith =>
|
||||
__$LanguageCopyWithImpl<_Language>(this, _$identity);
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return _$$_LanguageToJson(this);
|
||||
}
|
||||
}
|
||||
|
||||
abstract class _Language extends Language {
|
||||
const factory _Language(String code, String name) = _$_Language;
|
||||
const _Language._() : super._();
|
||||
|
||||
factory _Language.fromJson(Map<String, dynamic> json) = _$_Language.fromJson;
|
||||
|
||||
@override
|
||||
|
||||
/// ISO 639-1 code of this language.
|
||||
String get code => throw _privateConstructorUsedError;
|
||||
@override
|
||||
|
||||
/// Full English name of this language.
|
||||
String get name => throw _privateConstructorUsedError;
|
||||
@override
|
||||
@JsonKey(ignore: true)
|
||||
_$LanguageCopyWith<_Language> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
|
@ -6,12 +6,13 @@ part of 'language.dart';
|
|||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
Language _$LanguageFromJson(Map<String, dynamic> json) => Language(
|
||||
_$_Language _$$_LanguageFromJson(Map<String, dynamic> json) => _$_Language(
|
||||
json['code'] as String,
|
||||
json['name'] as String,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$LanguageToJson(Language instance) => <String, dynamic>{
|
||||
Map<String, dynamic> _$$_LanguageToJson(_$_Language instance) =>
|
||||
<String, dynamic>{
|
||||
'code': instance.code,
|
||||
'name': instance.name,
|
||||
};
|
||||
|
|
|
@ -16,7 +16,6 @@ class CommentsClient {
|
|||
|
||||
/// Returns the json parsed comments map.
|
||||
Future<Map<String, dynamic>> _getCommentJson(
|
||||
String service,
|
||||
String continuation,
|
||||
String clickTrackingParams,
|
||||
String xsfrToken,
|
||||
|
@ -25,14 +24,9 @@ class CommentsClient {
|
|||
final url = Uri(
|
||||
scheme: 'https',
|
||||
host: 'www.youtube.com',
|
||||
path: '/comment_service_ajax',
|
||||
path: '/next',
|
||||
queryParameters: {
|
||||
service: '1',
|
||||
'pbj': '1',
|
||||
'ctoken': continuation,
|
||||
'continuation': continuation,
|
||||
'itct': clickTrackingParams,
|
||||
'type': 'next',
|
||||
'key': 'AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8',
|
||||
});
|
||||
|
||||
return retry(() async {
|
||||
|
@ -70,8 +64,9 @@ class CommentsClient {
|
|||
|
||||
Stream<Comment> _getComments(String continuation, String clickTrackingParams,
|
||||
String xsfrToken, String visitorInfoLive, String ysc) async* {
|
||||
var data = await _getCommentJson('action_get_comments', continuation,
|
||||
clickTrackingParams, xsfrToken, visitorInfoLive, ysc);
|
||||
// contents.twoColumnWatchNextResults.results.results.contents[2](firstWhere itemSectionRenderer != null).itemSectionRenderer.contents[0].continuationItemRenderer
|
||||
var data = await _getCommentJson(
|
||||
continuation, clickTrackingParams, xsfrToken, visitorInfoLive, ysc);
|
||||
var contentRoot = data
|
||||
.get('response')
|
||||
?.get('continuationContents')
|
||||
|
@ -142,24 +137,11 @@ class CommentsClient {
|
|||
}
|
||||
}
|
||||
|
||||
//TODO: Implement replies
|
||||
/* Stream<Comment> getReplies(Video video, Comment comment) async* {
|
||||
if (video.watchPage == null || comment.continuation == null
|
||||
|| comment.clicktrackingParams == null) {
|
||||
Stream<Comment> getReplies(Video video, Comment comment) async* {
|
||||
if (video.watchPage == null ||
|
||||
comment.continuation == null ||
|
||||
comment.clicktrackingParams == null) {
|
||||
return;
|
||||
}
|
||||
yield* _getReplies(
|
||||
video.watchPage.initialData.continuation,
|
||||
video.watchPage.initialData.clickTrackingParams,
|
||||
video.watchPage.xsfrToken,
|
||||
video.watchPage.visitorInfoLive,
|
||||
video.watchPage.ysc);
|
||||
}
|
||||
|
||||
Stream<Comment> _getReplies(String continuation, String clickTrackingParams,
|
||||
String xsfrToken, String visitorInfoLive, String ysc) async* {
|
||||
var data = await _getCommentJson('action_get_comment_replies', continuation,
|
||||
clickTrackingParams, xsfrToken, visitorInfoLive, ysc);
|
||||
print(data);
|
||||
}*/
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import '../../reverse_engineering/cipher/cipher_operations.dart';
|
||||
import '../../reverse_engineering/responses/responses.dart';
|
||||
import '../../reverse_engineering/models/stream_info_provider.dart';
|
||||
|
||||
///
|
||||
class StreamContext {
|
||||
|
|
|
@ -2,7 +2,12 @@ import '../../exceptions/exceptions.dart';
|
|||
import '../../extensions/helpers_extension.dart';
|
||||
import '../../reverse_engineering/cipher/cipher_operations.dart';
|
||||
import '../../reverse_engineering/heuristics.dart';
|
||||
import '../../reverse_engineering/responses/responses.dart';
|
||||
import '../../reverse_engineering/pages/embed_page.dart';
|
||||
import '../../reverse_engineering/pages/watch_page.dart';
|
||||
import '../../reverse_engineering/dash_manifest.dart';
|
||||
import '../../reverse_engineering/player/player_source.dart';
|
||||
import '../../reverse_engineering/models/stream_info_provider.dart';
|
||||
import '../../reverse_engineering/responses/video_info_response.dart';
|
||||
import '../../reverse_engineering/youtube_http_client.dart';
|
||||
import '../video_id.dart';
|
||||
import 'bitrate.dart';
|
||||
|
|
|
@ -5,7 +5,7 @@ import 'package:meta/meta.dart';
|
|||
|
||||
import '../channels/channel_id.dart';
|
||||
import '../common/common.dart';
|
||||
import '../reverse_engineering/responses/responses.dart';
|
||||
import '../reverse_engineering/pages/watch_page.dart';
|
||||
import 'video_id.dart';
|
||||
|
||||
part 'video.freezed.dart';
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import '../channels/channel_id.dart';
|
||||
import '../common/common.dart';
|
||||
import '../extensions/helpers_extension.dart';
|
||||
import '../reverse_engineering/responses/responses.dart';
|
||||
import '../reverse_engineering/pages/watch_page.dart';
|
||||
import '../reverse_engineering/responses/video_info_response.dart';
|
||||
import '../reverse_engineering/youtube_http_client.dart';
|
||||
import 'closed_captions/closed_caption_client.dart';
|
||||
import 'comments/comments_client.dart';
|
||||
|
|
|
@ -5,7 +5,7 @@ version: 1.9.10
|
|||
homepage: https://github.com/Hexer10/youtube_explode_dart
|
||||
|
||||
environment:
|
||||
sdk: '>=2.12.0 <3.0.0'
|
||||
sdk: '>=2.13.0 <3.0.0'
|
||||
|
||||
dependencies:
|
||||
collection: ^1.15.0
|
||||
|
|
Loading…
Reference in New Issue