commit
5a94b500d2
|
@ -1,3 +1,6 @@
|
||||||
|
## 1.9.1
|
||||||
|
- Bug fixes (due to youtube changes)
|
||||||
|
|
||||||
## 1.9.0
|
## 1.9.0
|
||||||
- Support nnbd (dart 1.12)
|
- Support nnbd (dart 1.12)
|
||||||
- New api: `getQuerySuggestions`: Returns the suggestions youtube provides while making a video search.
|
- New api: `getQuerySuggestions`: Returns the suggestions youtube provides while making a video search.
|
||||||
|
|
|
@ -2,6 +2,8 @@ library _youtube_explode.retry;
|
||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:http/http.dart';
|
||||||
|
|
||||||
import 'exceptions/exceptions.dart';
|
import 'exceptions/exceptions.dart';
|
||||||
|
|
||||||
/// Run the [function] each time an exception is thrown until the retryCount
|
/// Run the [function] each time an exception is thrown until the retryCount
|
||||||
|
@ -28,7 +30,8 @@ Future<T> retry<T>(FutureOr<T> Function() function) async {
|
||||||
int getExceptionCost(Exception e) {
|
int getExceptionCost(Exception e) {
|
||||||
if (e is TransientFailureException ||
|
if (e is TransientFailureException ||
|
||||||
e is FormatException ||
|
e is FormatException ||
|
||||||
e is SearchItemSectionException) {
|
e is SearchItemSectionException ||
|
||||||
|
e is ClientException) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
if (e is RequestLimitExceededException) {
|
if (e is RequestLimitExceededException) {
|
||||||
|
|
|
@ -12,18 +12,19 @@ class EmbedPage {
|
||||||
static final _playerConfigExp =
|
static final _playerConfigExp =
|
||||||
RegExp('[\'""]PLAYER_CONFIG[\'""]\\s*:\\s*(\\{.*\\})');
|
RegExp('[\'""]PLAYER_CONFIG[\'""]\\s*:\\s*(\\{.*\\})');
|
||||||
static final _playerConfigExp2 = RegExp(r'yt.setConfig\((\{.*\})');
|
static final _playerConfigExp2 = RegExp(r'yt.setConfig\((\{.*\})');
|
||||||
|
static final _playerConfigExp3 = RegExp(r'ytcfg.set\((\{.*\})');
|
||||||
|
|
||||||
final Document root;
|
final Document root;
|
||||||
late final EmbedPlayerConfig? playerConfig = getPlayerConfig();
|
late final EmbedPlayerConfig? playerConfig = getPlayerConfig();
|
||||||
|
|
||||||
///
|
///
|
||||||
String? get sourceUrl {
|
String? get sourceUrl {
|
||||||
var url = root
|
final url = root
|
||||||
.querySelectorAll('*[name="player_ias/base"]')
|
.querySelectorAll('script')
|
||||||
.map((e) => e.attributes['src'])
|
.map((e) => e.attributes['src'])
|
||||||
.where((e) => !e.isNullOrWhiteSpace)
|
.whereNotNull()
|
||||||
.firstWhere((e) => e!.contains('player_ias') && e.endsWith('.js'),
|
.firstWhereOrNull((e) => e.contains('player_ias') && e.endsWith('.js'));
|
||||||
orElse: () => null);
|
|
||||||
// _root.querySelector('*[name="player_ias/base"]').attributes['src'];
|
// _root.querySelector('*[name="player_ias/base"]').attributes['src'];
|
||||||
if (url == null) {
|
if (url == null) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -34,7 +35,8 @@ class EmbedPage {
|
||||||
///
|
///
|
||||||
EmbedPlayerConfig? getPlayerConfig() {
|
EmbedPlayerConfig? getPlayerConfig() {
|
||||||
var playerConfigJson =
|
var playerConfigJson =
|
||||||
(_playerConfigJson ?? _playerConfigJson2)?.extractJson();
|
(_playerConfigJson3 ?? _playerConfigJson2 ?? _playerConfigJson)
|
||||||
|
?.extractJson();
|
||||||
if (playerConfigJson == null) {
|
if (playerConfigJson == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -53,6 +55,12 @@ class EmbedPage {
|
||||||
.map((e) => _playerConfigExp2.firstMatch(e)?.group(1))
|
.map((e) => _playerConfigExp2.firstMatch(e)?.group(1))
|
||||||
.firstWhereOrNull((e) => !e.isNullOrWhiteSpace);
|
.firstWhereOrNull((e) => !e.isNullOrWhiteSpace);
|
||||||
|
|
||||||
|
String? get _playerConfigJson3 => root
|
||||||
|
.getElementsByTagName('script')
|
||||||
|
.map((e) => e.text)
|
||||||
|
.map((e) => _playerConfigExp3.firstMatch(e)?.group(1))
|
||||||
|
.firstWhereOrNull((e) => !e.isNullOrWhiteSpace);
|
||||||
|
|
||||||
///
|
///
|
||||||
EmbedPage(this.root);
|
EmbedPage(this.root);
|
||||||
|
|
||||||
|
@ -62,6 +70,7 @@ class EmbedPage {
|
||||||
///
|
///
|
||||||
static Future<EmbedPage> get(YoutubeHttpClient httpClient, String videoId) {
|
static Future<EmbedPage> get(YoutubeHttpClient httpClient, String videoId) {
|
||||||
var url = 'https://youtube.com/embed/$videoId?hl=en';
|
var url = 'https://youtube.com/embed/$videoId?hl=en';
|
||||||
|
// final url = 'http://localhost:8080/embed/$videoId?hl=en';
|
||||||
return retry(() async {
|
return retry(() async {
|
||||||
var raw = await httpClient.getString(url);
|
var raw = await httpClient.getString(url);
|
||||||
return EmbedPage.parse(raw);
|
return EmbedPage.parse(raw);
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
|
import 'package:youtube_explode_dart/src/retry.dart';
|
||||||
|
|
||||||
import '../exceptions/exceptions.dart';
|
import '../exceptions/exceptions.dart';
|
||||||
import '../videos/streams/streams.dart';
|
import '../videos/streams/streams.dart';
|
||||||
|
@ -12,15 +13,16 @@ class YoutubeHttpClient extends http.BaseClient {
|
||||||
final Map<String, String> _defaultHeaders = const {
|
final Map<String, String> _defaultHeaders = const {
|
||||||
'user-agent':
|
'user-agent':
|
||||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.111 Safari/537.36',
|
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.111 Safari/537.36',
|
||||||
'accept-language': 'en-US,en;q=1.0',
|
'cookie': 'CONSENT=YES+cb',
|
||||||
'x-youtube-client-name': '1',
|
'accept':
|
||||||
'x-youtube-client-version': '2.20200609.04.02',
|
'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9',
|
||||||
'x-spf-previous': 'https://www.youtube.com/',
|
'accept-language': 'accept-language: en-US,en;q=0.9',
|
||||||
'x-spf-referer': 'https://www.youtube.com/',
|
'sec-fetch-dest': 'document',
|
||||||
'x-youtube-device':
|
'sec-fetch-mode': 'navigate',
|
||||||
'cbr=Chrome&cbrver=81.0.4044.138&ceng=WebKit&cengver=537.36'
|
'sec-fetch-site': 'none',
|
||||||
'&cos=Windows&cosver=10.0',
|
'sec-fetch-user': '?1',
|
||||||
'x-youtube-page-label': 'youtube.ytfe.desktop_20200617_1_RC1'
|
'sec-gpc': '1',
|
||||||
|
'upgrade-insecure-requests': '1'
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Initialize an instance of [YoutubeHttpClient]
|
/// Initialize an instance of [YoutubeHttpClient]
|
||||||
|
@ -99,7 +101,7 @@ class YoutubeHttpClient extends http.BaseClient {
|
||||||
try {
|
try {
|
||||||
final request = http.Request('get', url);
|
final request = http.Request('get', url);
|
||||||
request.headers['range'] = 'bytes=$i-${i + 9898989 - 1}';
|
request.headers['range'] = 'bytes=$i-${i + 9898989 - 1}';
|
||||||
final response = await send(request);
|
final response = await retry(() => send(request));
|
||||||
if (validate) {
|
if (validate) {
|
||||||
_validateResponse(response, response.statusCode);
|
_validateResponse(response, response.statusCode);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
name: youtube_explode_dart
|
name: youtube_explode_dart
|
||||||
description: A port in dart of the youtube explode library. Supports several API functions without the need of Youtube API Key.
|
description: A port in dart of the youtube explode library. Supports several API functions without the need of Youtube API Key.
|
||||||
version: 1.9.0
|
version: 1.9.1
|
||||||
|
|
||||||
homepage: https://github.com/Hexer10/youtube_explode_dart
|
homepage: https://github.com/Hexer10/youtube_explode_dart
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,8 @@ void main() {
|
||||||
group('Get streams manifest of any video', () {
|
group('Get streams manifest of any video', () {
|
||||||
for (final val in {
|
for (final val in {
|
||||||
VideoId('9bZkp7q19f0'), // very popular
|
VideoId('9bZkp7q19f0'), // very popular
|
||||||
// VideoId('SkRSXFQerZs'), // age restricted (embed allowed) - This is unplayable
|
VideoId(
|
||||||
|
'SkRSXFQerZs'), // age restricted (embed allowed) - This is unplayable
|
||||||
VideoId('hySoCSoH-g8'), // age restricted (embed not allowed)
|
VideoId('hySoCSoH-g8'), // age restricted (embed not allowed)
|
||||||
VideoId('_kmeFXjjGfk'), // embed not allowed (type 1)
|
VideoId('_kmeFXjjGfk'), // embed not allowed (type 1)
|
||||||
VideoId('MeJVWBSsPAY'), // embed not allowed (type 2)
|
VideoId('MeJVWBSsPAY'), // embed not allowed (type 2)
|
||||||
|
|
Loading…
Reference in New Issue