Index: Slim/Player/StreamingController.pm =================================================================== --- Slim/Player/StreamingController.pm (revision 30446) +++ Slim/Player/StreamingController.pm (working copy) @@ -901,7 +901,10 @@ $seekdata = $song->getSeekDataByPosition($bytesReceived); } - if (!$bytesReceived || $seekdata) { + if ($seekdata && $seekdata->{'streamComplete'}) { + main::INFOLOG && $log->is_info && $log->info("stream already complete at offset $bytesReceived"); + _Streamout($self); + } elsif (!$bytesReceived || $seekdata) { main::INFOLOG && $log->is_info && $log->info("Restarting stream at offset $bytesReceived"); _Stream($self, $event, {song => $song, seekdata => $seekdata, reconnect => 1}); if ($song == playingSong($self)) { Index: Slim/Player/Protocols/HTTP.pm =================================================================== --- Slim/Player/Protocols/HTTP.pm (revision 30446) +++ Slim/Player/Protocols/HTTP.pm (working copy) @@ -371,21 +371,34 @@ $length = $rangeLength; } - # However we got here, we want to know that we did not start at the beginning, if possible - if ($startOffset && $length) { + my $song = ${*self}{'song'} if blessed $self; + + if (!$song && $client->controller()->songStreamController()) { + $song = $client->controller()->songStreamController()->song(); + } + + if ($song && $length) { + my $seekdata = $song->seekdata(); - # Assume saved duration is more accurate that by calculating from length and bitrate - my $duration = Slim::Music::Info::getDuration($url); - $duration ||= $length * 8 / $bitrate if $bitrate; + if ($startOffset && $seekdata && $seekdata->{restartOffset} + && $seekdata->{sourceStreamOffset} && $startOffset > $seekdata->{sourceStreamOffset}) + { + $startOffset = $seekdata->{sourceStreamOffset}; + } + + my $streamLength = $length; + $streamLength -= $startOffset if $startOffset; + $song->streamLength($streamLength); - if ($duration) { - my $song = ${*self}{'song'} if blessed $self; + # However we got here, we want to know that we did not start at the beginning, if possible + if ($startOffset) { - if (!$song && $client->controller()->songStreamController()) { - $song = $client->controller()->songStreamController()->song(); - } + + # Assume saved duration is more accurate that by calculating from length and bitrate + my $duration = Slim::Music::Info::getDuration($url); + $duration ||= $length * 8 / $bitrate if $bitrate; - if ($song) { + if ($duration) { main::INFOLOG && $directlog->info("Setting startOffest based on Content-Range to ", $duration * ($startOffset/$length)); $song->startOffset($duration * ($startOffset/$length)); } @@ -564,7 +577,7 @@ # If seeking, add Range header if ($client && $seekdata) { - $request .= $CRLF . 'Range: bytes=' . int( $seekdata->{sourceStreamOffset} ) . '-'; + $request .= $CRLF . 'Range: bytes=' . int( $seekdata->{sourceStreamOffset} + $seekdata->{restartOffset}) . '-'; if (defined $seekdata->{timeOffset}) { # Fix progress bar @@ -833,7 +846,9 @@ sub getSeekDataByPosition { my ($class, $client, $song, $bytesReceived) = @_; - return {sourceStreamOffset => $bytesReceived}; + my $seekdata = $song->seekdata() || {}; + + return {%$seekdata, restartOffset => $bytesReceived}; } # reinit is used on SN to maintain seamless playback when bumped to another instance Index: Slim/Player/Protocols/File.pm =================================================================== --- Slim/Player/Protocols/File.pm (revision 30446) +++ Slim/Player/Protocols/File.pm (working copy) @@ -53,7 +53,7 @@ my $track = $song->currentTrack(); my $url = $track->url; my $client = $args->{'client'}; - my $seekdata = $args->{'song'}->seekdata(); + my $seekdata = $song->seekdata(); my $seekoffset = 0; @@ -118,6 +118,7 @@ my $format = Slim::Music::Info::contentType($track); my $seekoffset = $offset; + my $streamLength = $size; if (defined $seekdata) { @@ -122,7 +123,7 @@ if (defined $seekdata) { if ( ! $seekdata->{sourceStreamOffset} - && ! $seekdata->{playingStreamOffset} + && ! $seekdata->{restartOffset} && $seekdata->{'timeOffset'} && canSeek($class, $client, $song) ) { @@ -129,10 +130,12 @@ $seekdata->{sourceStreamOffset} = _timeToOffset($sock, $format, $song, $seekdata->{'timeOffset'}); } - if ($seekdata->{sourceStreamOffset}) { # used for seeking + if ($seekdata->{restartOffset}) { # used for reconnect + $streamLength = $song->streamLength(); + $seekoffset = $seekdata->{restartOffset}; + elsif ($seekdata->{sourceStreamOffset}) { # used for seeking $seekoffset = $seekdata->{sourceStreamOffset}; - } elsif ($seekdata->{playingStreamOffset}) { # used for reconnect - $seekoffset = $offset + $seekdata->{playingStreamOffset}; + $streamLength -= $seekdata->{sourceStreamOffset} - $offset; } else { $seekoffset = $offset; # normal case } @@ -145,7 +148,13 @@ ${*$sock}{'streamFormat'} = $args->{'transcoder'}->{'streamformat'}; - if ( $seekoffset ) { + if ( $seekoffset + + # We do not need to worry about an initialAudioBlock when we are restarting + # as getSeekDataByPosition() will not have allowed a restart within the + # initialAudioBlock. + && !($seekdata && $seekdata->{restartOffset}) ) + { my $streamClass = _streamClassForFormat($format); if (!defined($song->initialAudioBlock()) && @@ -162,9 +171,11 @@ if ($seekoffset <= $length) { # Might as well just play from the start normally $offset = $seekoffset = 0; + $streamLength = $size + $offset; } else { ${*$sock}{'initialAudioBlockRemaining'} = $length; ${*$sock}{'initialAudioBlockRef'} = \($song->initialAudioBlock()); + $streamLength += $length; } } } @@ -183,6 +194,8 @@ } else { $client->songBytes(0); } + + $song->streamLength($streamLength); return $sock; } @@ -309,9 +322,24 @@ } sub getSeekDataByPosition { - my (undef, undef, undef, $bytesReceived) = @_; + my (undef, undef, $song, $bytesReceived) = @_; + + my $streamLength = $song->streamLength(); + + if ( !$streamLength + || $song->initialAudioBlock() && $bytesReceived < $song->initialAudioBlock() ) + { + return undef; + } + + my $position = $song->totalbytes() - ($streamLength - $bytesReceived); - return {playingStreamOffset => $bytesReceived}; + if ($position <= 0) { + return undef; + } + + my $seekdata = $song->seekdata || {}; # We preserve the original seekdata so we know the time-offset, if any + return {%$seekdata, restartOffset => $position + $song->offset()}; } sub canSeek { Index: Slim/Player/Song.pm =================================================================== --- Slim/Player/Song.pm (revision 30446) +++ Slim/Player/Song.pm (working copy) @@ -61,7 +61,7 @@ qw( _status - startOffset + startOffset streamLength seekdata initialAudioBlock _canSeek _canSeekError @@ -722,6 +722,12 @@ return undef if $self->_transcoded(); + my $streamLength = $self->streamLength(); + + if ($streamLength && $bytesReceived >= $streamLength) { + return {streamComplete => 1}; + } + my $handler = $self->currentTrackHandler(); if ($handler->can('getSeekDataByPosition')) {