@@ -15,25 +15,22 @@ use crate::stream::{LiveStream, LiveStreamOptions};
15
15
use crate :: structs:: FFmpegArgs ;
16
16
17
17
use crate :: {
18
- constants:: { BASE_URL , DEFAULT_MAX_RETRIES } ,
18
+ constants:: { BASE_URL , DEFAULT_DL_CHUNK_SIZE , DEFAULT_MAX_RETRIES , INNERTUBE_CLIENT } ,
19
19
info_extras:: { get_media, get_related_videos} ,
20
20
stream:: { NonLiveStream , NonLiveStreamOptions , Stream } ,
21
21
structs:: {
22
22
CustomRetryableStrategy , PlayerResponse , VideoError , VideoInfo , VideoOptions , YTConfig ,
23
23
} ,
24
24
utils:: {
25
- between, choose_format, clean_video_details, get_functions, get_html, get_html5player ,
26
- get_random_v6_ip, get_video_id, get_ytconfig, is_age_restricted_from_html,
25
+ between, check_experiments , choose_format, clean_video_details, get_functions, get_html,
26
+ get_html5player , get_random_v6_ip, get_video_id, get_ytconfig, is_age_restricted_from_html,
27
27
is_not_yet_broadcasted, is_play_error, is_private_video, is_rental,
28
28
parse_live_video_formats, parse_video_formats, sort_formats,
29
29
} ,
30
30
} ;
31
31
32
- // 10485760 -> Default is 10MB to avoid Youtube throttle (Bigger than this value can be throttle by Youtube)
33
- pub ( crate ) const DEFAULT_DL_CHUNK_SIZE : u64 = 10485760 ;
34
-
35
32
#[ derive( Clone , derive_more:: Display , derivative:: Derivative ) ]
36
- #[ display( fmt = "Video({video_id})" ) ]
33
+ #[ display( "Video({video_id})" ) ]
37
34
#[ derivative( Debug , PartialEq , Eq ) ]
38
35
pub struct Video {
39
36
video_id : String ,
@@ -102,7 +99,10 @@ impl Video {
102
99
}
103
100
} ;
104
101
105
- let max_retries = options. request_options . max_retries . unwrap_or ( DEFAULT_MAX_RETRIES ) ;
102
+ let max_retries = options
103
+ . request_options
104
+ . max_retries
105
+ . unwrap_or ( DEFAULT_MAX_RETRIES ) ;
106
106
107
107
let retry_policy = ExponentialBackoff :: builder ( )
108
108
. retry_bounds ( Duration :: from_millis ( 1000 ) , Duration :: from_millis ( 30000 ) )
@@ -177,8 +177,31 @@ impl Video {
177
177
return Err ( VideoError :: VideoIsPrivate ) ;
178
178
}
179
179
180
+ // POToken experiment detected fallback to ios client (Webpage contains broken formats)
181
+ if check_experiments ( & response) {
182
+ let ios_ytconfig = self
183
+ . get_player_ytconfig (
184
+ & response,
185
+ INNERTUBE_CLIENT . get ( "ios" ) . cloned ( ) . unwrap_or_default ( ) ,
186
+ )
187
+ . await ?;
188
+
189
+ let player_response_new =
190
+ serde_json:: from_str :: < PlayerResponse > ( & ios_ytconfig) . unwrap_or_default ( ) ;
191
+
192
+ player_response. streaming_data = player_response_new. streaming_data ;
193
+ }
194
+
180
195
if is_age_restricted {
181
- let embed_ytconfig = self . get_embeded_ytconfig ( & response) . await ?;
196
+ let embed_ytconfig = self
197
+ . get_player_ytconfig (
198
+ & response,
199
+ INNERTUBE_CLIENT
200
+ . get ( "tv_embedded" )
201
+ . cloned ( )
202
+ . unwrap_or_default ( ) ,
203
+ )
204
+ . await ?;
182
205
183
206
let player_response_new =
184
207
serde_json:: from_str :: < PlayerResponse > ( & embed_ytconfig) . unwrap_or_default ( ) ;
@@ -484,39 +507,36 @@ impl Video {
484
507
}
485
508
486
509
#[ cfg_attr( feature = "performance_analysis" , flamer:: flame) ]
487
- async fn get_embeded_ytconfig ( & self , html : & str ) -> Result < String , VideoError > {
510
+ async fn get_player_ytconfig (
511
+ & self ,
512
+ html : & str ,
513
+ configs : ( & str , & str , & str ) ,
514
+ ) -> Result < String , VideoError > {
515
+ use std:: str:: FromStr ;
516
+
488
517
let ytcfg = get_ytconfig ( html) ?;
489
518
490
- // This client can access age restricted videos (unless the uploader has disabled the 'allow embedding' option)
491
- // See: https://github.com/yt-dlp/yt-dlp/blob/28d485714fef88937c82635438afba5db81f9089/yt_dlp/extractor/youtube.py#L231
492
- let query = serde_json:: json!( {
493
- "context" : {
494
- "client" : {
495
- "clientName" : "TVHTML5_SIMPLY_EMBEDDED_PLAYER" ,
496
- "clientVersion" : "2.0" ,
497
- "hl" : "en" ,
498
- "clientScreen" : "EMBED" ,
499
- } ,
500
- "thirdParty" : {
501
- "embedUrl" : "https://google.com" ,
502
- } ,
503
- } ,
504
- "playbackContext" : {
505
- "contentPlaybackContext" : {
506
- "signatureTimestamp" : ytcfg. sts. unwrap_or( 0 ) ,
507
- "html5Preference" : "HTML5_PREF_WANTS" ,
508
- } ,
509
- } ,
510
- "videoId" : self . get_video_id( ) ,
511
- } ) ;
519
+ let client = configs. 2 ;
520
+ let sts = ytcfg. sts . unwrap_or ( 0 ) ;
521
+ let video_id = self . get_video_id ( ) ;
522
+
523
+ let query = serde_json:: from_str :: < serde_json:: Value > ( & format ! (
524
+ r#"{{
525
+ {client}
526
+ "playbackContext": {{
527
+ "contentPlaybackContext": {{
528
+ "signatureTimestamp": {sts},
529
+ "html5Preference": "HTML5_PREF_WANTS"
530
+ }}
531
+ }},
532
+ "videoId": "{video_id}"
533
+ }}"#
534
+ ) )
535
+ . unwrap_or_default ( ) ;
512
536
513
537
static CONFIGS : Lazy < ( HeaderMap , & str ) > = Lazy :: new ( || {
514
- use std:: str:: FromStr ;
515
-
516
538
( HeaderMap :: from_iter ( [
517
539
( HeaderName :: from_str ( "content-type" ) . unwrap ( ) , HeaderValue :: from_str ( "application/json" ) . unwrap ( ) ) ,
518
- ( HeaderName :: from_str ( "X-Youtube-Client-Name" ) . unwrap ( ) , HeaderValue :: from_str ( "85" ) . unwrap ( ) ) ,
519
- ( HeaderName :: from_str ( "X-Youtube-Client-Version" ) . unwrap ( ) , HeaderValue :: from_str ( "2.0" ) . unwrap ( ) ) ,
520
540
( HeaderName :: from_str ( "Origin" ) . unwrap ( ) , HeaderValue :: from_str ( "https://www.youtube.com" ) . unwrap ( ) ) ,
521
541
( HeaderName :: from_str ( "User-Agent" ) . unwrap ( ) , HeaderValue :: from_str ( "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3513.0 Safari/537.36" ) . unwrap ( ) ) ,
522
542
( HeaderName :: from_str ( "Referer" ) . unwrap ( ) , HeaderValue :: from_str ( "https://www.youtube.com/" ) . unwrap ( ) ) ,
@@ -526,10 +546,20 @@ impl Video {
526
546
] ) , "AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8" )
527
547
} ) ;
528
548
549
+ let mut headers = CONFIGS . 0 . clone ( ) ;
550
+ headers. insert (
551
+ HeaderName :: from_str ( "X-Youtube-Client-Version" ) . unwrap ( ) ,
552
+ HeaderValue :: from_str ( configs. 0 ) . unwrap ( ) ,
553
+ ) ;
554
+ headers. insert (
555
+ HeaderName :: from_str ( "X-Youtube-Client-Name" ) . unwrap ( ) ,
556
+ HeaderValue :: from_str ( configs. 1 ) . unwrap ( ) ,
557
+ ) ;
558
+
529
559
let response = self
530
560
. client
531
561
. post ( "https://www.youtube.com/youtubei/v1/player" )
532
- . headers ( CONFIGS . 0 . clone ( ) )
562
+ . headers ( headers )
533
563
. query ( & [ ( "key" , CONFIGS . 1 ) ] )
534
564
. json ( & query)
535
565
. send ( )
@@ -550,15 +580,12 @@ async fn get_m3u8(
550
580
url : & str ,
551
581
client : & reqwest_middleware:: ClientWithMiddleware ,
552
582
) -> Result < Vec < ( String , String ) > , VideoError > {
553
- let base_url = Url :: parse ( BASE_URL ) . expect ( "BASE_URL corrapt" ) ;
554
- let base_url_host = base_url. host_str ( ) . expect ( "BASE_URL host corrapt" ) ;
583
+ let base_url = Url :: parse ( BASE_URL ) ? ;
584
+ let base_url_host = base_url. host_str ( ) ;
555
585
556
586
let url = Url :: parse ( url)
557
587
. and_then ( |mut x| {
558
- let set_host_result = x. set_host ( Some ( base_url_host) ) ;
559
- if set_host_result. is_err ( ) {
560
- return Err ( set_host_result. expect_err ( "How can be posible" ) ) ;
561
- }
588
+ x. set_host ( base_url_host) ?;
562
589
Ok ( x)
563
590
} )
564
591
. map ( |x| x. as_str ( ) . to_string ( ) )
@@ -574,19 +601,12 @@ async fn get_m3u8(
574
601
. split ( '\n' )
575
602
. filter ( |x| HTTP_REGEX . is_match ( x) && ITAG_REGEX . is_match ( x) ) ;
576
603
577
- let itag_and_url: Vec < ( String , String ) > = itag_and_url
578
- . map ( |line| {
579
- let itag = ITAG_REGEX
580
- . captures ( line)
581
- . expect ( "IMPOSSIBLE" )
582
- . get ( 1 )
583
- . map ( |x| x. as_str ( ) )
584
- . unwrap_or ( "" ) ;
585
-
586
- // println!("itag: {}, url: {}", itag, line);
587
- ( itag. to_string ( ) , line. to_string ( ) )
604
+ Ok ( itag_and_url
605
+ . filter_map ( |line| {
606
+ ITAG_REGEX . captures ( line) . and_then ( |caps| {
607
+ caps. get ( 1 )
608
+ . map ( |itag| ( itag. as_str ( ) . to_string ( ) , line. to_string ( ) ) )
609
+ } )
588
610
} )
589
- . collect ( ) ;
590
-
591
- Ok ( itag_and_url)
611
+ . collect :: < Vec < ( String , String ) > > ( ) )
592
612
}
0 commit comments