Skip to content

Commit

Permalink
Merge pull request #612 from philippe44/song/track/handlerForUrl-refa…
Browse files Browse the repository at this point in the history
…ctoring-attempt

Song/track/handler for url refactoring attempt
  • Loading branch information
mherger committed Jun 14, 2021
2 parents 4025956 + 853c4cb commit a6f1371
Show file tree
Hide file tree
Showing 17 changed files with 364 additions and 162 deletions.
260 changes: 260 additions & 0 deletions DEVELOPERS.txt

Large diffs are not rendered by default.

26 changes: 16 additions & 10 deletions SOCKS.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
SSH tunnel with port forwarding allows per port TCP/UDP traffic to be
"forwarded" from one end to the tunnel to the other and appear like it was
initiated from the remote end of tunnel. For example all traffic send to
port 5000 on the local machine can be forwared to a remote machine, on port
port 5000 on the local machine can be forwarded to a remote machine, on port
25000 and will appear to any client on the remote side as if it was locally
coming from port 25000

Expand All @@ -13,22 +13,31 @@ This tunneling is not a all-or-nothing tunnel an allow each TCP request to be
selectively forwarding or not

You must first either use a public SOCKS server or create your own SOCKS/SSH
pair in which ase you must have a local SOCKS server, a local SSH client and
a remote SSH server. On Linux openssh does everything, one local instance with
pair in which case you must have a local SOCKS server, a local SSH client and
a remote SSH server. On Linux, openssh does everything, one local instance with
dynamic port forwarding (-D) and a remote instance to a friend's network does
the job. On Windows, you can use Bitvise Client & Server. There is plenty of
internet litterature on SOCKS/SSH that explain the concept much better than
anything I could write :-)

HTTP request ------> SOCKS client ------||-----> SOCKS server ------> www.google.com
>www.google.com >some.socks.com || >www.google.com >5000
>5000 >1080 || >5000 (from some.socks.com)

HTTP request ------> SOCKS client ----> SOCKS relay (encrypted) SOCKS server -----> www.google.com
>www.google.com >192.168.0.1 >SSH client -----||----> >SSH server 5000
>5000 >1080 >some.ssh.com || >www.google.com
>23 || >5000 (from some.ssh.com)

One thing to notice with SOCKS5 is the lack of proper authentication mechanism
which means that if you have a username/password, they will be sent in clear
to the server. SOCKS4 does not require authentication. That's why I prefer
to have a local SOCKS server that creates a SSH tunnel to a remote end but this
means you must have a SSH remote end.

To use socks proxy to your HTTP requests, set the SOCKS parameter in LMS and
simply passed a hash named "socks" to SimpleAsyncHTTP::new or Async::HTP::new
with the following content (see IO::Socket::Socks)
To use socks proxy to your HTTP requests, simply passed a hash named "socks"
to SimpleAsyncHTTP::new or Async::HTTP::new with the following content. See
also IO::Socket::Socks)

my $http = Slim::Networking::SimpleAsyncHTTP->new(
sub { # succes CB },
Expand Down Expand Up @@ -69,7 +78,4 @@ are set and to 4 otherwise. ProxyPort can be omitted and will be set to 1080.

If 'socks' hash is set but ProxyAddr is missing, a regular SimpleAsync or Async
call will be made





37 changes: 20 additions & 17 deletions Slim/Player/Protocols/HTTP.pm
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ sub request {
my $self = shift;
my $args = shift;
my $song = $args->{'song'};
my $track = $song->track;
my $track = $song->currentTrack;
my $processor = $track->processors($song->wantFormat);

# no other guidance, define AudioBlock to make sure that audio_offset is skipped in requestString
Expand Down Expand Up @@ -399,10 +399,10 @@ sub canEnhanceHTTP {
}

sub canDirectStream {
my ($classOrSelf, $client, $url, $inType) = @_;
my ($class, $client, $url, $inType) = @_;

# when persistent is used, we won't direct stream to enable retries
return 0 if $classOrSelf->canEnhanceHTTP($client, $url);
return 0 if $class->canEnhanceHTTP($client, $url);

# When synced, we don't direct stream so that the server can proxy a single
# stream for all players
Expand Down Expand Up @@ -436,10 +436,10 @@ sub canDirectStreamSong {
my ( $class, $client, $song ) = @_;

# can't go direct if we are synced or proxy is set by user
my $direct = $class->canDirectStream( $client, $song->streamUrl(), $class->getFormatForURL() );
my $direct = $class->canDirectStream( $client, $song->streamUrl, $class->getFormatForURL );
return 0 unless $direct;

my $processor = $song->track->processors($song->wantFormat);
my $processor = $song->currentTrack->processors($song->wantFormat);

# no header or stripHeader flag has precedence
return $direct if $song->stripHeader || !$processor;
Expand Down Expand Up @@ -940,12 +940,13 @@ sub requestString {
# Always add Range to exclude trailing metadata or garbage (aif/mp4...)
if ($client) {
my $song = $client->streamingSong;
my $track = $song->currentTrack;
$client->songBytes(0);

my $first = $seekdata->{restartOffset} || int( $seekdata->{sourceStreamOffset} );
$first ||= $song->track->audio_offset if $song->stripHeader || defined $song->initialAudioBlock;
$first ||= $track->audio_offset if $song->stripHeader || defined $song->initialAudioBlock;
$request .= $CRLF . 'Range: bytes=' . ($first || 0) . '-';
$request .= $song->track->audio_offset + $song->track->audio_size - 1 if $song->track->audio_size;
$request .= $track->audio_offset + $track->audio_size - 1 if $track->audio_size;

if ($first) {

Expand All @@ -955,7 +956,7 @@ sub requestString {
$client->master()->remoteStreamStartTime( Time::HiRes::time() - $seekdata->{timeOffset} );
}

$client->songBytes( $first - ($song->stripHeader ? $song->track->audio_offset : 0) );
$client->songBytes( $first - ($song->stripHeader ? $track->audio_offset : 0) );
}
}

Expand Down Expand Up @@ -1029,7 +1030,9 @@ sub getMetadataFor {
# Check for parsed WMA metadata, this is here because WMA may
# use HTTP protocol handler
my $song = $client->playingSong();
if ( $song && $song->track->url eq $url ) {
my $current = $song->currentTrack->url eq $url if $song;

if ( $current ) {
if ( my $meta = $song->pluginData('wmaMeta') ) {
my $data = {};
if ( $meta->{artist} ) {
Expand Down Expand Up @@ -1067,7 +1070,7 @@ sub getMetadataFor {
# Remember playlist URL
my $playlistURL = $url;

# Check for radio URLs with cached covers
# Check for radio or OPML feeds URLs with cached covers
my $cache = Slim::Utils::Cache->new();
my $cover = $cache->get( "remote_image_$url" );

Expand Down Expand Up @@ -1105,11 +1108,11 @@ sub getMetadataFor {
}
}
else {
# make sure that protocol handler is what the $song wanted, not just the $url-based one
my $handler = $current ? $song->currentTrackHandler : Slim::Player::ProtocolHandlers->handlerForURL($url);

if ( (my $handler = Slim::Player::ProtocolHandlers->handlerForURL($url)) !~ /^(?:$class|Slim::Player::Protocols::MMS|Slim::Player::Protocols::HTTPS?)$/ ) {
if ( $handler && $handler->can('getMetadataFor') ) {
return $handler->getMetadataFor( $client, $url );
}
if ( $handler && $handler !~ /^(?:$class|Slim::Player::Protocols::MMS|Slim::Player::Protocols::HTTPS?)$/ && $handler->can('getMetadataFor') ) {
return $handler->getMetadataFor( $client, $url );
}

my $type = uc( $track->content_type || '' ) . ' ' . Slim::Utils::Strings::cstring($client, 'RADIO');
Expand Down Expand Up @@ -1193,11 +1196,11 @@ sub getSeekData {
main::INFOLOG && $log->info( "Trying to seek $newtime seconds into $bitrate kbps" );

my $offset = int (( ( $bitrate * 1000 ) / 8 ) * $newtime);
$offset -= $offset % ($song->track->block_alignment || 1);
$offset -= $offset % ($song->currentTrack->block_alignment || 1);

# this might be re-calculated by request() if direct streaming is disabled
return {
sourceStreamOffset => $offset + $song->track->audio_offset,
sourceStreamOffset => $offset + $song->currentTrack->audio_offset,
timeOffset => $newtime,
};
}
Expand All @@ -1208,7 +1211,7 @@ sub getSeekDataByPosition {
my $seekdata = $song->seekdata() || {};

my $position = int($seekdata->{'sourceStreamOffset'}) || 0;
$position ||= $song->track->audio_offset if defined $song->initialAudioBlock;
$position ||= $song->currentTrack->audio_offset if defined $song->initialAudioBlock;

return {%$seekdata, restartOffset => $position + $bytesReceived - $song->initialAudioBlock};
}
Expand Down
8 changes: 4 additions & 4 deletions Slim/Player/Protocols/HTTPS.pm
Original file line number Diff line number Diff line change
Expand Up @@ -68,23 +68,23 @@ sub close {

# Check whether the current player can stream HTTPS or Url is HTTP
sub canDirectStream {
my $self = shift;
my $class = shift;
my ($client, $url) = @_;

if ( $client->canHTTPS || $url =~ /^http:/) {
return $self->SUPER::canDirectStream(@_);
return $class->SUPER::canDirectStream(@_);
}

return 0;
}

# Check whether the current player can stream HTTPS or Url is HTTP
sub canDirectStreamSong {
my $self = shift;
my $class = shift;
my ($client, $song) = @_;

if ( $client->canHTTPS || $song->streamUrl =~ /^http:/) {
return $self->SUPER::canDirectStreamSong(@_);
return $class->SUPER::canDirectStreamSong(@_);
}

return 0;
Expand Down
2 changes: 1 addition & 1 deletion Slim/Player/Protocols/MMS.pm
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ sub randomGUID {
}

sub canDirectStream {
my ($classOrSelf, $client, $url, $inType) = @_;
my ($class, $client, $url, $inType) = @_;

# When synced, we don't direct stream so that the server can proxy a single
# stream for all players
Expand Down
13 changes: 7 additions & 6 deletions Slim/Player/SoftSqueeze.pm
Original file line number Diff line number Diff line change
Expand Up @@ -99,16 +99,17 @@ sub canDecodeRhapsody { 0 };
sub hasScrolling { 0 }

sub canDirectStream {
my $client = shift;
my $url = shift;
my ($client, $url, $song) = @_;

my $handler = Slim::Player::ProtocolHandlers->handlerForURL($url);
# this is client's canDirectStream, not protocol handler's
my $handler = $song->currentTrackHandler;
return unless $handler && !$handler->isa("Slim::Player::Protocols::MMS");

if ($handler && $handler->can("canDirectStream") && !$handler->isa("Slim::Player::Protocols::MMS")) {
if ($handler->can("canDirectStreamSong")) {
return $handler->canDirectStreamSong($client, $song);
} elsif ($handler->can("canDirectStream")) {
return $handler->canDirectStream($client, $url);
}

return undef;
}


Expand Down
25 changes: 1 addition & 24 deletions Slim/Player/Song.pm
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ sub new {
handler => $handler,
_track => $track,
streamUrl => $url, # May get updated later, either here or in handler
originUrl => $url, # Keep track of the non-redirected url
originUrl => $url, # keep trace of the url the song was created with
);

$self->seekdata($seekdata) if $seekdata;
Expand Down Expand Up @@ -884,29 +884,6 @@ sub canDoSeek {
}
}

# This is a prototype, that just falls back to protocol-handler providers (pull) for now.
# It is planned to move the actual metadata maintenance into this module where the
# protocol-handlers will push the data.

sub metadata {
my ($self) = @_;

my $handler;

if (($handler = $self->_currentTrackHandler()) && $handler->can('songMetadata')
|| ($handler = $self->handler()) && $handler->can('songMetadata') )
{
return $handler->songMetadata($self);
}
elsif (($handler = $self->_currentTrackHandler()) && $handler->can('getMetadataFor')
|| ($handler = $self->handler()) && $handler->can('getMetadataFor') )
{
return $handler->songMetadata($self->master, $self->currentTrackHandler()->url, 0);
}

return undef;
}

sub icon {
my $self = shift;
my $client = $self->master();
Expand Down
43 changes: 20 additions & 23 deletions Slim/Player/SongStreamController.pm
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package Slim::Player::SongStreamController;


# Logitech Media Server Copyright 2001-2020 Logitech.
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License,
Expand All @@ -9,23 +8,30 @@ package Slim::Player::SongStreamController;
use bytes;
use strict;

use base qw(Slim::Utils::Accessor);

use Slim::Utils::Log;

my $log = logger('player.source');

my $_liveCount = 0;

{
__PACKAGE__->mk_accessor('ro', qw(song streamHandler streamUrlHandler));
__PACKAGE__->mk_accessor('rw', qw(playerProxyStreaming));
}

sub new {
my ($class, $song, $streamHandler) = @_;

my $self = $class->SUPER::new;

my $self = {
$self->init_accessor(
song => $song,
streamHandler => $streamHandler,
protocolHandler => my $handler = Slim::Player::ProtocolHandlers->handlerForURL($song->streamUrl()),
};
streamUrlHandler => Slim::Player::ProtocolHandlers->handlerForURL($song->streamUrl()),
);

bless $self, $class;

$_liveCount++;
if (main::DEBUGLOG && $log->is_debug) {
$log->debug("live=$_liveCount");
Expand All @@ -45,41 +51,32 @@ sub DESTROY {
}
}

sub song {return shift->{'song'};}
sub streamHandler {return shift->{'streamHandler'};}
sub protocolHandler {return shift->{'protocolHandler'};}

sub songProtocolHandler {return shift->song()->handler();}

sub close {
my $self = shift;

my $fd = $self->{'streamHandler'};
my $fd = $self->streamHandler;

if (defined $fd) {
Slim::Networking::Select::removeError($fd);
Slim::Networking::Select::removeRead($fd);
$fd->close;
delete $self->{'streamHandler'};
}
}

sub songHandler {
return shift->song->handler();
}

sub isDirect {
return shift->{'song'}->directstream() || 0;
return shift->song->directstream() || 0;
}

sub streamUrl {
return shift->{'song'}->streamUrl();
return shift->song->streamUrl();
}

sub track {
return shift->{'song'}->currentTrack();
}

sub playerProxyStreaming {
my $self = shift;
$self->{'playerProxyStreaming'} = shift if @_;
return $self->{'playerProxyStreaming'};
return shift->song->currentTrack();
}

1;

0 comments on commit a6f1371

Please sign in to comment.