From f4637f1f8cd8600d86430383fd910dd87126d8a5 Mon Sep 17 00:00:00 2001 From: Alexander Adam Date: Mon, 25 May 2026 23:08:42 +0200 Subject: [PATCH] pass yt-dlp http_headers through to actual fetches yt-dlp returns a per-format http_headers dict (User-Agent, Accept, Accept-Language, Sec-Fetch-Mode etc) that has to be sent on the videoplayback fetch, otherwise the CDN can reject the request. the plugin captured the dict into config->{headers} but never applied it to any of the HTTP::Request objects it builds. thread the headers through: - WebM/M4a/MPEGTS get an optional headers arg in new() - their probe HTTP::Requests carry the headers - sysread_URL and sendRequest pick the headers off config note: this fixes a real gap but is not enough on its own to play SABR-locked videos. that needs yt-dlp with a working JS runtime. --- plugin/M4a.pm | 31 ++++++++++++++++++------------- plugin/MPEGTS.pm | 23 +++++++++++++---------- plugin/ProtocolHandler.pm | 24 ++++++++++++++---------- plugin/WebM.pm | 36 +++++++++++++++++++++--------------- 4 files changed, 66 insertions(+), 48 deletions(-) diff --git a/plugin/M4a.pm b/plugin/M4a.pm index 3a957f8..910784a 100644 --- a/plugin/M4a.pm +++ b/plugin/M4a.pm @@ -45,25 +45,26 @@ use constant PARSING => 2; use constant DATA => 3; { - __PACKAGE__->mk_accessor('rw', qw(bitrate samplerate channels format url)); + __PACKAGE__->mk_accessor('rw', qw(bitrate samplerate channels format url http_headers)); __PACKAGE__->mk_accessor('rw', qw(_mp4a _context)); } sub new { - my ($class, $url) = @_; + my ($class, $url, $headers) = @_; my $self = $class->SUPER::new; # _context is to be flushed/initialized each time the getAudio is restarted - # but _mp4a is to be used for the duration of the objects, i.e. when seeking + # but _mp4a is to be used for the duration of the objects, i.e. when seeking $self->init_accessor( format => 'aac', url => $url, + http_headers => $headers || {}, _context => {}, _mp4a => {}, - ); - + ); + return bless $self, $class; -} +} sub flush { $_[0]->_context( { } ); @@ -87,13 +88,15 @@ sub getStartOffset { my $http = Slim::Networking::Async::HTTP->new; my $args = { startTime => $startTime }; - + + my %hdrs = %{ $self->http_headers || {} }; + $http->send_request( { - request => HTTP::Request->new( GET => $url ), - + request => HTTP::Request->new( GET => $url, [ %hdrs ] ), + onStream => sub { my ($http, $dataref) = @_; - + if (my $atom = parseAtoms('sidx', $dataref, $args)) { my $offset = $args->{offset} || 0; $startTime -= $atom->{time}; @@ -128,12 +131,14 @@ sub initialize { my ($self, $cb, $ecb, $url) = @_; my $http = Slim::Networking::Async::HTTP->new; my $args = {}; - + # we might have received an initialize url $url ||= $self->url; - + + my %hdrs = %{ $self->http_headers || {} }; + $http->send_request( { - request => HTTP::Request->new( GET => $url ), + request => HTTP::Request->new( GET => $url, [ %hdrs ] ), onStream => sub { my ($http, $dataref) = @_; diff --git a/plugin/MPEGTS.pm b/plugin/MPEGTS.pm index ac43ec4..8775450 100644 --- a/plugin/MPEGTS.pm +++ b/plugin/MPEGTS.pm @@ -32,22 +32,23 @@ use constant TABLE_SYNTAX_OFS => TABLE_OFS + 3; use constant TABLE_DATA_OFS => TABLE_SYNTAX_OFS + 5; { - __PACKAGE__->mk_accessor('rw', qw(bitrate samplerate channels format url)); + __PACKAGE__->mk_accessor('rw', qw(bitrate samplerate channels format url http_headers)); __PACKAGE__->mk_accessor('rw', qw( _context)); } sub new { - my ($class, $url) = @_; + my ($class, $url, $headers) = @_; my $self = $class->SUPER::new; - + # _context is to be flushed/initialized each time the getAudio is restarted - $self->init_accessor( + $self->init_accessor( url => $url, + http_headers => $headers || {}, _context => {}, - ); - + ); + return bless $self, $class; -} +} sub flush { $_[0]->_context( { } ); @@ -69,9 +70,11 @@ sub getStartOffset { sub initialize { my ($self, $cb, $ecb, $fragment) = @_; - + + my %hdrs = %{ $self->http_headers || {} }; + # let's get a bit of 1st fragment to get AAC params - Slim::Networking::SimpleAsyncHTTP->new ( + Slim::Networking::SimpleAsyncHTTP->new ( sub { my $outBuf =''; @@ -120,7 +123,7 @@ sub initialize { $ecb->(); }, - )->get( $fragment, Range => 'bytes=0-128000' ); + )->get( $fragment, %hdrs, Range => 'bytes=0-128000' ); } sub getAudio { diff --git a/plugin/ProtocolHandler.pm b/plugin/ProtocolHandler.pm index 15ac0e0..0e83a25 100644 --- a/plugin/ProtocolHandler.pm +++ b/plugin/ProtocolHandler.pm @@ -301,11 +301,11 @@ sub sysread_URL { # we only use one session in that mode my $session = $v->{sessions}->[0] ||= Slim::Networking::Async::HTTP->new; - my $request = HTTP::Request->new( GET => $url, - [ 'Connection', 'keep-alive', - 'Range', "bytes=$v->{offset}-" . ($v->{offset} + DATA_CHUNK - 1), - ] - ); + my %hdrs = %{ $config->{headers} || {} }; + $hdrs{Connection} = 'keep-alive'; + $hdrs{Range} = "bytes=$v->{offset}-" . ($v->{offset} + DATA_CHUNK - 1); + + my $request = HTTP::Request->new( GET => $url, [ %hdrs ] ); $request->protocol( 'HTTP/1.1' ); @@ -539,8 +539,12 @@ AUDIO: sub sendRequest { my ($self, $url, $level, $onBody, $onError) = @_; my $v = ${*$self}{vars}; - - my $request = HTTP::Request->new( GET => $url, [ 'Connection', 'keep-alive' ] ); + my $config = ${*$self}{config}; + + my %hdrs = %{ ($config && $config->{headers}) || {} }; + $hdrs{Connection} = 'keep-alive'; + + my $request = HTTP::Request->new( GET => $url, [ %hdrs ] ); # my $request = HTTP::Request->new( GET => $url ); $request->protocol( 'HTTP/1.1' ); @@ -758,8 +762,8 @@ sub _getNextTrack { # What type of stream do we have? if (!$track->{manifest_url}) { my $handler = $config->{'format'} =~ /aac/ ? - Plugins::YouTube::M4a->new($track->{url}) : - Plugins::YouTube::WebM->new($track->{url}); + Plugins::YouTube::M4a->new($track->{url}, $track->{http_headers}) : + Plugins::YouTube::WebM->new($track->{url}, $track->{http_headers}); $config->{'handler'} = $handler; $config->{'sysread'} = \&sysread_URL; @@ -816,7 +820,7 @@ sub _getNextTrack { # stash that into pluginData to retrieve it later $song->pluginData(stash => $mpeg); - my $handler = Plugins::YouTube::MPEGTS->new( $track->{url} ); + my $handler = Plugins::YouTube::MPEGTS->new( $track->{url}, $track->{http_headers} ); $config->{source} = 'hls-mpeg'; $config->{handler} = $handler; diff --git a/plugin/WebM.pm b/plugin/WebM.pm index 4a8dab8..3dc5f5a 100644 --- a/plugin/WebM.pm +++ b/plugin/WebM.pm @@ -71,24 +71,25 @@ use constant ID_CUE_CLUSTER_POS => "\xF1"; my $log = logger('plugin.youtube'); { - __PACKAGE__->mk_accessor('rw', qw(bitrate samplerate channels format url)); + __PACKAGE__->mk_accessor('rw', qw(bitrate samplerate channels format url http_headers)); __PACKAGE__->mk_accessor('rw', qw(_webm _context)); } sub new { - my ($class, $url) = @_; + my ($class, $url, $headers) = @_; my $self = $class->SUPER::new; - + # _context is to be flushed/initialized each time the getAudio is restarted - # but _webm is to be used for the duration of the objects, i.e. when seeking + # but _webm is to be used for the duration of the objects, i.e. when seeking $self->init_accessor( url => $url, + http_headers => $headers || {}, _context => {}, _webm => {}, - ); - + ); + return bless $self, $class; -} +} sub flush { $_[0]->_context( { } ); @@ -655,12 +656,15 @@ sub getStartOffset { 'need' => EBML_NEED, }; + my %hdrs = %{ $self->http_headers || {} }; + $hdrs{Range} = "bytes=$webm->{offset}->{cues}-"; + $http->send_request( { - request => HTTP::Request->new( GET => $url, [ 'Range' => "bytes=$webm->{offset}->{cues}-" ] ), - onStream => sub { + request => HTTP::Request->new( GET => $url, [ %hdrs ] ), + onStream => sub { my ($http, $dataref) = @_; - - $var->{'inBuf'} .= $$dataref; + + $var->{'inBuf'} .= $$dataref; my $res = getCues($var); if ( $res eq WEBM_MORE ) { @@ -693,12 +697,14 @@ sub initialize { }; my $http = Slim::Networking::Async::HTTP->new; - + + my %hdrs = %{ $self->http_headers || {} }; + $http->send_request( { - request => HTTP::Request->new( GET => $self->url ), - onStream => sub { + request => HTTP::Request->new( GET => $self->url, [ %hdrs ] ), + onStream => sub { my ($http, $dataref) = @_; - + $var->{'inBuf'} .= $$dataref; my $res = $self->getHeaders($var);