Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Persistent & Cache streaming options alltogether #591

Merged

Conversation

philippe44
Copy link
Contributor

I think I have found a pretty neat way to alleviate some concerns due to buffered HTTP and have the option of "Persistent" (aka Reliable) connections. I thought it was not possible when I did the "Reliable" plugin but with what I've learnt during the development of "Buffered" I think this works. It requires much more testing, though.

It's different from buffer as it does not subclass HTTPS, instead it is installed at the core of HTTP. What is really nice is that as long as the original session does not fail, it is exactly that session which is used for streaming (as opposed to "Reliable" which was always creating its own session). When the connection fails (0 bytes returned while not all expected bytes received, then it will kick a new, hidden, session that is a clone of the original one and resume streaming from there. The original socket is not active anymore but I've not seen in LMS a case where opened() would be called once body is streamed. If there is, we can always add an opened() method as well. But worse case, it works as LMS does today.

I've like to keep the "Buffered" option because it can player nice with some services but you can't have "buffered" and "persistent" at the same time as "buffered" relies on adding an onRead() to original socket and if it fails, then it will not be called anymore. Now (and I still need to make that), if a plugin decides to create a "Buffered" ProtocolHandler, the "persistent" will simply disabled.

@philippe44 philippe44 changed the title Persistent & Cache streaming option alltogether Persistent & Cache streaming options alltogether Apr 18, 2021
@philippe44
Copy link
Contributor Author

I've been able to fold the "Buffered" version under ¨S::P::P::HTTP as well. that's pretty cool because now all subclass of S::P::P::HTTP can use either Persistent or Buffered, depending on system's settings. There is no need for plugins to know about what's happening, so I've removed the changes in Tidal. It is also dynamic, i.e. no need to restart LMS.

Still, this PR is not ready for merge because there is one pending issue with HTTPS: I need to have a call to HTTP's close() so that I can cleanup Persistent/Buffered but as HTTPS is multiple inheritance, then its close() calls IO::Socket::SSL, and not S::P::P::HTTP::close(), so objects are not properly destroyed.

@philippe44
Copy link
Contributor Author

philippe44 commented Apr 19, 2021

Last PR takes properly care of HTTPS' multiple inheritance case. Now we have the option of "Persistent" or "Buffered" mode for any HTTP(S) connection and PH that depend on it and they don't need to make anything special. When playing tracks with unknown content-length (webradio) "Bufferred" will automatically downgrade to "Persistent".

I've tried to handle the case of the user changing the mode while a track is playing, so any track started in a mode will continue to be handled as it was created.

NB: with the way it works now, we can easily have a blacklist of URLs for which this is disabled

just add 'useEnhancedHTTP' = 0/1/2 if the $args of the new()
- create a contentRange method
- make enhancement a method that can be overloaded by plugins instead of a new() argument
otherwise plugin that overload parseDirectHeader will not set the value and the "peristent" mode will fail if we started from an offset
So that temp files can be stored anywhere and everybody is happy
@philippe44
Copy link
Contributor Author

I think this is now complete. I've added a --tmpdir option to slimserver.pl command line so that there is a choice where to put tmp files but that's not bloating the UI (only 'motivated' users will find the option)

Copy link
Member

@michaelherger michaelherger left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR looks amazing! (says the man who didn't understand half of it)

Please see the few comments.

my $enhanced = $self->canEnhanceHTTP($args->{'client'}, $args->{'url'});
return unless $enhanced && $self->contentLength;

if ($enhanced == 1) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please define a const for the various enhanced modes to make such conditions self explanatory.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, except that I don't know how I could use these in the setting's HTML file

HTML/EN/settings/server/performance.html Outdated Show resolved Hide resolved
Slim/Player/Protocols/HTTP.pm Outdated Show resolved Hide resolved
Slim/Player/Protocols/HTTP.pm Show resolved Hide resolved
my ($server, $port, $path) = Slim::Utils::Misc::crackURL($args->{'url'});

# need to change URI if proxy is used as request does not include it
my $uri = $prefs->get('webproxy') && $server !~ /(?:localhost|127.0.0.1)/ ? "http://$server:$port$path" : $args->{'url'};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this check would deserve a little helper function, as it's being done in a hand full of places... I know, more changes in unrelated places. But as you're doing a great job changing some of the core already, let's take the opportunity to get rid of some of the cruft at the same time.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It will be super-thin over crackURL, really (one line). Unless you agree to add a one return parameter to crackUrl to include the proxied url, something like

sub crackURL {
	my ($string) = @_;

	my $urlstring = join('|', Slim::Player::ProtocolHandlers->registeredHandlers);

	$string =~ m|(?:$urlstring)://(?:([^\@\/:]+):?([^\@\/]*)\@)?([^:/]+):*(\d*)(\S*)|i;

	my ($user, $pass, $host, $port, $path) = ($1, $2, $3, $4, $5);

	$path ||= '/';
	$port ||= ((Slim::Networking::Async::HTTP->hasSSL() && $string =~ /^https/) ? 443 : 80);
	
	my $proxied = "http://$host:$port$path" if $prefs->get('webproxy') && $host !~ /(?:localhost|127.0.0.1)/;

	if ( main::DEBUGLOG && $ospathslog->is_debug ) {
		$ospathslog->debug("Cracked: $string with [$host],[$port],[$path]");
		$ospathslog->debug("   user: [$user]") if $user;
		$ospathslog->debug("   pass: [$pass]") if $pass;
	}

	return ($host, $port, $path, $user, $pass, $proxied);
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done that

Slim/Player/Protocols/HTTP.pm Outdated Show resolved Hide resolved
Slim/Player/Protocols/HTTP.pm Outdated Show resolved Hide resolved
Slim/Formats/Movie.pm Show resolved Hide resolved
Slim/Player/Source.pm Outdated Show resolved Hide resolved
Copy link
Contributor

@mherger mherger left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's give this a try! Unless you want to fix that minor typo in the latest comment?

@mherger mherger merged commit ee3cb56 into LMS-Community:public/8.2 Apr 25, 2021
@mherger
Copy link
Contributor

mherger commented Apr 25, 2021

Many, many thanks!

@philippe44
Copy link
Contributor Author

Fingers crossed 😄

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants