From 470be598fa1f286d3240aef3a64da27b80b4ca55 Mon Sep 17 00:00:00 2001 From: philippe44 Date: Wed, 20 Dec 2023 21:04:53 -0800 Subject: [PATCH 01/11] initial commit --- Slim/Player/Playlist.pm | 50 ++++++++++++++++-------------- Slim/Player/StreamingController.pm | 18 ++++++++--- 2 files changed, 41 insertions(+), 27 deletions(-) diff --git a/Slim/Player/Playlist.pm b/Slim/Player/Playlist.pm index 7e19b7294c0..df5d53c5c50 100644 --- a/Slim/Player/Playlist.pm +++ b/Slim/Player/Playlist.pm @@ -382,25 +382,25 @@ sub removeTrack { } if (!$stopped) { - if (Slim::Player::Source::streamingSongIndex($client) >= $tracknum && Slim::Player::Source::streamingSongIndex($client) < $tracknum + $nTracks) { + + my $index = Slim::Player::Source::streamingSongIndex($client); + my $queue = $client->currentsongqueue(); + + # udpate index of tracks in queue but capture the streamingIndex before... + for my $song (@$queue) { + if ($tracknum < $song->index()) { + $song->index($song->index() - $nTracks); + } + } + + if ($index >= $tracknum && $index < $tracknum + $nTracks) { # If we're removing the streaming song (which is different from # the playing song), get the client to flush out the current song # from its audio pipeline. main::INFOLOG && $log->info("Removing currently streaming track."); - Slim::Player::Source::flushStreamingSong($client); + } - } else { - - my $queue = $client->currentsongqueue(); - - for my $song (@$queue) { - - if ($tracknum < $song->index()) { - $song->index($song->index() - $nTracks); - } - } - } } if ($stopped) { @@ -589,18 +589,10 @@ sub moveSong { splice @{$listref},$dest, 0, @item; + my $queue = $client->currentsongqueue(); + my $playingIndex = Slim::Player::Source::playingSongIndex($client); my $streamingIndex = Slim::Player::Source::streamingSongIndex($client); - # If we're streaming a different song than we're playing and - # moving either to or from the streaming song position, flush - # the streaming song, because it's no longer relevant. - if (($playingIndex != $streamingIndex) && - (($streamingIndex == $src) || ($streamingIndex == $dest) || - ($playingIndex == $src) || ($playingIndex == $dest))) { - Slim::Player::Source::flushStreamingSong($client); - } - - my $queue = $client->currentsongqueue(); for my $song (@$queue) { my $index = $song->index(); @@ -611,6 +603,15 @@ sub moveSong { $song->index(($dest>$src)? $index - 1 : $index + 1); } } + + # If we're streaming a different song than we're playing and + # moving either to or from the streaming song position, flush + # the streaming song, because it's no longer relevant. + if (($playingIndex != $streamingIndex) && + (($streamingIndex == $src) || ($streamingIndex == $dest) || + ($playingIndex == $src) || ($playingIndex == $dest))) { + Slim::Player::Source::flushStreamingSong($client); + } refreshPlaylist($client); } @@ -1076,6 +1077,9 @@ sub modifyPlaylistCallback { my $client = $request->client(); + # inform controller (mainly in case last track was already fully streamed) + $client->controller->playlistUpdated(); + main::INFOLOG && $log->info("Checking if persistPlaylists is set.."); if ( !$client || !$prefs->get('persistPlaylists') ) { diff --git a/Slim/Player/StreamingController.pm b/Slim/Player/StreamingController.pm index 690d23c576a..0041746d4a5 100644 --- a/Slim/Player/StreamingController.pm +++ b/Slim/Player/StreamingController.pm @@ -147,8 +147,8 @@ Flush => [ [ \&_Invalid, \&_BadState, \&_BadState, \&_Invalid], # STOPPED [ \&_BadState, \&_Invalid, \&_Invalid, \&_BadState], # BUFFERING [ \&_BadState, \&_Invalid, \&_Invalid, \&_BadState], # WAITING_TO_SYNC - [ \&_Invalid, \&_FlushGetNext,\&_FlushGetNext,\&_Invalid], # PLAYING - [ \&_Invalid, \&_FlushGetNext,\&_FlushGetNext,\&_Invalid], # PAUSED + [ \&_Invalid, \&_FlushGetNext,\&_FlushGetNext,\&_FlushGetNext], # PLAYING + [ \&_Invalid, \&_FlushGetNext,\&_FlushGetNext,\&_FlushGetNext], # PAUSED ], Skip => [ [ \&_StopGetNext, \&_BadState, \&_BadState, \&_NoOp], # STOPPED @@ -164,7 +164,6 @@ JumpToTime => [ \&_JumpToTime, \&_JumpToTime, \&_JumpToTime, \&_JumpToTime], # PLAYING [ \&_JumpPaused, \&_JumpPaused, \&_JumpPaused, \&_JumpPaused], # PAUSED ], - NextTrackReady => [ [ \&_NoOp, \&_BadState, \&_BadState, \&_Stream], # STOPPED [ \&_BadState, \&_Invalid, \&_Invalid, \&_BadState], # BUFFERING @@ -186,7 +185,6 @@ LocalEndOfStream => [ \&_Invalid, \&_Streamout, \&_Invalid, \&_Invalid], # PLAYING [ \&_Invalid, \&_Streamout, \&_Invalid, \&_Invalid], # PAUSED ], - BufferReady => [ [ \&_Invalid, \&_BadState, \&_BadState, \&_Invalid], # STOPPED [ \&_BadState, \&_WaitToSync, \&_WaitToSync, \&_BadState], # BUFFERING @@ -979,6 +977,9 @@ sub _Skip { sub _FlushGetNext { # flush -> Idle; IF [moreTracks] THEN getNextTrack -> TrackWait ENDIF my ($self, $event, $params) = @_; + # flush means that we get rid of the streaming song + shift $self->{'songqueue'}; + foreach my $player (@{$self->{'players'}}) { $player->flush(); } @@ -2112,6 +2113,15 @@ sub closeStream { $_[0]->{'songStreamController'}->close() if $_[0]->{'songStreamController'}; } +sub playlistUpdated { + my ($self) = @_; + + if ($self->{'streamingState'} == IDLE && $self->{'playingState'} != STOPPED) { + main::INFOLOG && $log->info("$self->{'masterId'} playlist updated after end of last track's streaming"); + _getNextTrack($self); + } +} + #################################################################### # Incoming events - <> PlayControl From 987da5fcd9b8291512a1315b4326dc9ae42570a3 Mon Sep 17 00:00:00 2001 From: philippe44 Date: Thu, 21 Dec 2023 15:47:40 -0800 Subject: [PATCH 02/11] handle STMf in stream controller to move to next track --- Slim/Player/SqueezeSlave.pm | 5 ++++- Slim/Player/Squeezebox2.pm | 5 ++++- Slim/Player/StreamingController.pm | 10 ++++++++++ 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/Slim/Player/SqueezeSlave.pm b/Slim/Player/SqueezeSlave.pm index 86aaf7e50b3..137da867bf0 100644 --- a/Slim/Player/SqueezeSlave.pm +++ b/Slim/Player/SqueezeSlave.pm @@ -241,7 +241,10 @@ sub pcm_sample_rates { sub statHandler { my ($client, $code) = @_; - if ($code eq 'STMd') { + if ($code eq 'STMf') { + $client->readyToStream(1); + $client->controller()->playerFlushed($client); + } elsif ($code eq 'STMd') { $client->readyToStream(1); $client->controller()->playerReadyToStream($client); } elsif ($code eq 'STMn') { diff --git a/Slim/Player/Squeezebox2.pm b/Slim/Player/Squeezebox2.pm index 00f2a318b86..51fba14a1a2 100644 --- a/Slim/Player/Squeezebox2.pm +++ b/Slim/Player/Squeezebox2.pm @@ -147,7 +147,10 @@ sub statHandler { } - if ($code eq 'STMd') { + if ($code eq 'STMf') { + $client->readyToStream(1); + $client->controller()->playerFlushed($client); + } elsif ($code eq 'STMd') { $client->readyToStream(1); $client->controller()->playerReadyToStream($client); } elsif ($code eq 'STMn') { diff --git a/Slim/Player/StreamingController.pm b/Slim/Player/StreamingController.pm index 0041746d4a5..bf37bd23957 100644 --- a/Slim/Player/StreamingController.pm +++ b/Slim/Player/StreamingController.pm @@ -2223,6 +2223,16 @@ sub playerTrackStarted { Slim::Buttons::Common::syncPeriodicUpdates($client, Time::HiRes::time() + 0.1); } +sub playerFlushed { + my ($self, $client) = @_; + + main::INFOLOG && $log->info($client->id); + + # STMf received as a result of strm 'q' shall be ignored. Otherwise it means + # we have flushed the streaming track and are ready to stream again + _eventAction($self, 'ReadyToStream') if $self->{'playingState'} != STOPPED; +} + sub playerReadyToStream { my ($self, $client) = @_; From bcbf5a5a234f36b0a1d5e2cb4a1d5239ce6f51ed Mon Sep 17 00:00:00 2001 From: philippe44 Date: Thu, 21 Dec 2023 17:21:09 -0800 Subject: [PATCH 03/11] simplified version with player::flush setting readytostream state --- Slim/Player/SqueezeSlave.pm | 5 +---- Slim/Player/Squeezebox2.pm | 8 ++++---- Slim/Player/StreamingController.pm | 10 ---------- 3 files changed, 5 insertions(+), 18 deletions(-) diff --git a/Slim/Player/SqueezeSlave.pm b/Slim/Player/SqueezeSlave.pm index 137da867bf0..86aaf7e50b3 100644 --- a/Slim/Player/SqueezeSlave.pm +++ b/Slim/Player/SqueezeSlave.pm @@ -241,10 +241,7 @@ sub pcm_sample_rates { sub statHandler { my ($client, $code) = @_; - if ($code eq 'STMf') { - $client->readyToStream(1); - $client->controller()->playerFlushed($client); - } elsif ($code eq 'STMd') { + if ($code eq 'STMd') { $client->readyToStream(1); $client->controller()->playerReadyToStream($client); } elsif ($code eq 'STMn') { diff --git a/Slim/Player/Squeezebox2.pm b/Slim/Player/Squeezebox2.pm index 51fba14a1a2..56335fdd813 100644 --- a/Slim/Player/Squeezebox2.pm +++ b/Slim/Player/Squeezebox2.pm @@ -147,10 +147,7 @@ sub statHandler { } - if ($code eq 'STMf') { - $client->readyToStream(1); - $client->controller()->playerFlushed($client); - } elsif ($code eq 'STMd') { + if ($code eq 'STMd') { $client->readyToStream(1); $client->controller()->playerReadyToStream($client); } elsif ($code eq 'STMn') { @@ -392,6 +389,9 @@ sub flush { $client->stream('f'); $client->SUPER::flush(); + + # once flush, don't wait for answer, just get ready + $client->readyToStream(1); return 1; } diff --git a/Slim/Player/StreamingController.pm b/Slim/Player/StreamingController.pm index bf37bd23957..0041746d4a5 100644 --- a/Slim/Player/StreamingController.pm +++ b/Slim/Player/StreamingController.pm @@ -2223,16 +2223,6 @@ sub playerTrackStarted { Slim::Buttons::Common::syncPeriodicUpdates($client, Time::HiRes::time() + 0.1); } -sub playerFlushed { - my ($self, $client) = @_; - - main::INFOLOG && $log->info($client->id); - - # STMf received as a result of strm 'q' shall be ignored. Otherwise it means - # we have flushed the streaming track and are ready to stream again - _eventAction($self, 'ReadyToStream') if $self->{'playingState'} != STOPPED; -} - sub playerReadyToStream { my ($self, $client) = @_; From dd19ec97ad0510d8f7f4b24f93c965d01c041349 Mon Sep 17 00:00:00 2001 From: philippe44 Date: Fri, 22 Dec 2023 22:24:28 -0800 Subject: [PATCH 04/11] shift on scalar is deprecated on recent perl --- Slim/Player/StreamingController.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Slim/Player/StreamingController.pm b/Slim/Player/StreamingController.pm index 0041746d4a5..7de9b1d57d7 100644 --- a/Slim/Player/StreamingController.pm +++ b/Slim/Player/StreamingController.pm @@ -978,7 +978,7 @@ sub _FlushGetNext { # flush -> Idle; IF [moreTracks] THEN getNextTrack -> Trac my ($self, $event, $params) = @_; # flush means that we get rid of the streaming song - shift $self->{'songqueue'}; + shift @{$self->{'songqueue'}}; foreach my $player (@{$self->{'players'}}) { $player->flush(); From 20929d34bd950667bca5611dfc786bea54f88057 Mon Sep 17 00:00:00 2001 From: philippe44 Date: Sat, 23 Dec 2023 00:40:56 -0800 Subject: [PATCH 05/11] last track modification in case it has been fully streamed should be in move/add (it's deletion is to be checked for shuffle) --- Slim/Player/Playlist.pm | 3 --- 1 file changed, 3 deletions(-) diff --git a/Slim/Player/Playlist.pm b/Slim/Player/Playlist.pm index df5d53c5c50..44d20d96c69 100644 --- a/Slim/Player/Playlist.pm +++ b/Slim/Player/Playlist.pm @@ -1077,9 +1077,6 @@ sub modifyPlaylistCallback { my $client = $request->client(); - # inform controller (mainly in case last track was already fully streamed) - $client->controller->playlistUpdated(); - main::INFOLOG && $log->info("Checking if persistPlaylists is set.."); if ( !$client || !$prefs->get('persistPlaylists') ) { From 6b1cae86b76e8ff2c4d0973096a4786cccbf204e Mon Sep 17 00:00:00 2001 From: philippe44 Date: Sat, 23 Dec 2023 17:07:37 -0800 Subject: [PATCH 06/11] complete last track add/remove/move issue --- Slim/Player/Playlist.pm | 22 ++++++++++++++++++++++ Slim/Player/StreamingController.pm | 14 ++++++++++---- 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/Slim/Player/Playlist.pm b/Slim/Player/Playlist.pm index 44d20d96c69..7e624e74443 100644 --- a/Slim/Player/Playlist.pm +++ b/Slim/Player/Playlist.pm @@ -245,10 +245,21 @@ sub addTracks { }); } + my $playingIndex = Slim::Player::Source::playingSongIndex($client)+1; + my $streamingIndex = Slim::Player::Source::streamingSongIndex($client)+1; + if ($insert) { _insert_done($client, $canAdd); } + # if we are adding a track while we are playing the last one, might + # need to relaunch the process if it has been streamed fully. + if (($playingIndex == $streamingIndex) && + ($streamingIndex == count($client) - 1)) { + $log->info("adding track while playing last, check if streaming needs relaunch"); + $client->controller->nextIfStreamed($client); + } + return $canAdd; } @@ -610,9 +621,20 @@ sub moveSong { if (($playingIndex != $streamingIndex) && (($streamingIndex == $src) || ($streamingIndex == $dest) || ($playingIndex == $src) || ($playingIndex == $dest))) { + $log->info("moving a track right after playing one but it has been fully streamed"); Slim::Player::Source::flushStreamingSong($client); } + # if we are either moving the last track or moving one after it, we + # might need to relaunch the process if it has been streamed fully. + if (($playingIndex == $streamingIndex) && + ($streamingIndex == count($client) - 1) && + ($src == $streamingIndex || $dest == $streamingIndex)) { + $log->info("moving last track itsef or another track past it"); + $client->controller->nextIfStreamed($client); + } + + refreshPlaylist($client); } } diff --git a/Slim/Player/StreamingController.pm b/Slim/Player/StreamingController.pm index 7de9b1d57d7..8e16085c0e9 100644 --- a/Slim/Player/StreamingController.pm +++ b/Slim/Player/StreamingController.pm @@ -440,7 +440,7 @@ sub _CheckPaused { # only called when PAUSED } elsif (!$song->duration()) { # Bug 7620: stop remote radio streams if they have been paused long enough for the buffer to fill. - # Assume unknown duration means radio and so we shuould stop now + # Assume unknown duration means radio and so we should stop now main::INFOLOG && $log->info("Stopping remote stream upon full buffer when paused (no resume)"); _Stop(@_); @@ -2113,11 +2113,17 @@ sub closeStream { $_[0]->{'songStreamController'}->close() if $_[0]->{'songStreamController'}; } -sub playlistUpdated { +sub nextIfStreamed { my ($self) = @_; - if ($self->{'streamingState'} == IDLE && $self->{'playingState'} != STOPPED) { - main::INFOLOG && $log->info("$self->{'masterId'} playlist updated after end of last track's streaming"); + # this is called when we are adding/moving another track after last one + # or moving it upper in the list. If it has been fully streamed and we + # are playing/buffering/waiting, then we need to grab next track. If we + # are paused then we'll restart the process later upon resume + if ($self->{'streamingState'} == IDLE && + $self->{'playingState'} != STOPPED && + $self->{'playingState'} != PAUSED) { + main::INFOLOG && $log->info("getting next track to re-launch streaming process"); _getNextTrack($self); } } From 3dddf211f829158ba9214196b8c535d0c8a501c6 Mon Sep 17 00:00:00 2001 From: philippe44 Date: Fri, 29 Dec 2023 19:12:44 -0800 Subject: [PATCH 07/11] in insert_mode, use playinIndex, not streamingIndex! --- Slim/Player/Playlist.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Slim/Player/Playlist.pm b/Slim/Player/Playlist.pm index 7e624e74443..315166c8a76 100644 --- a/Slim/Player/Playlist.pm +++ b/Slim/Player/Playlist.pm @@ -266,7 +266,7 @@ sub addTracks { sub _insert_done { my ($client, $size, $callbackf, $callbackargs) = @_; - my $playlistIndex = Slim::Player::Source::streamingSongIndex($client)+1; + my $playlistIndex = Slim::Player::Source::playingSongIndex($client) + 1; my $moveFrom = count($client) - $size; if (shuffle($client)) { From 5be2022a190ad63f72ae449814c4bf18f109537f Mon Sep 17 00:00:00 2001 From: philippe44 Date: Sat, 30 Dec 2023 17:20:47 -0800 Subject: [PATCH 08/11] handle multiple remove --- Slim/Player/Playlist.pm | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Slim/Player/Playlist.pm b/Slim/Player/Playlist.pm index 315166c8a76..c5af85a713d 100644 --- a/Slim/Player/Playlist.pm +++ b/Slim/Player/Playlist.pm @@ -457,6 +457,7 @@ sub removeMultipleTracks { my $stopped = 0; my $oldMode = Slim::Player::Source::playmode($client); + my $flush = 0; my $playingTrackPos = ${shuffleList($client)}[Slim::Player::Source::playingSongIndex($client)]; my $streamingTrackPos = ${shuffleList($client)}[Slim::Player::Source::streamingSongIndex($client)]; @@ -484,7 +485,7 @@ sub removeMultipleTracks { } elsif ($streamingTrackPos == $oldCount) { - Slim::Player::Source::flushStreamingSong($client); + $flush = 1; } } else { @@ -550,6 +551,8 @@ sub removeMultipleTracks { for my $song (@{$queue}) { $song->index($oldToNewShuffled{$song->index()} || 0); } + + Slim::Player::Source::flushStreamingSong($client) if $flush; } refreshPlaylist($client); From 54b491073ffc69310da798a3b900d63a34f38e08 Mon Sep 17 00:00:00 2001 From: philippe44 Date: Sat, 30 Dec 2023 18:22:08 -0800 Subject: [PATCH 09/11] handle shuffle case in insert --- Slim/Player/Playlist.pm | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Slim/Player/Playlist.pm b/Slim/Player/Playlist.pm index c5af85a713d..a9f7a04f431 100644 --- a/Slim/Player/Playlist.pm +++ b/Slim/Player/Playlist.pm @@ -277,6 +277,11 @@ sub _insert_done { } else { push @{$client->shufflelist}, @reshuffled; } + # need to flush if we are changing streaming song + if (Slim::Player::Source::streamingSongIndex($client) == $playlistIndex % count($client)) { + main::INFOLOG && $log->info("add+replace streaming (not playing) track"); + Slim::Player::Source::flushStreamingSong($client); + } } else { if (count($client) != $size) { moveSong($client, $moveFrom, $playlistIndex, $size); @@ -624,7 +629,7 @@ sub moveSong { if (($playingIndex != $streamingIndex) && (($streamingIndex == $src) || ($streamingIndex == $dest) || ($playingIndex == $src) || ($playingIndex == $dest))) { - $log->info("moving a track right after playing one but it has been fully streamed"); + $log->info("move+replace streaming (not playing) track"); Slim::Player::Source::flushStreamingSong($client); } From 976dcd768f673d8bbbbd37d064970f89c1099ff7 Mon Sep 17 00:00:00 2001 From: philippe44 Date: Sun, 31 Dec 2023 23:17:56 -0800 Subject: [PATCH 10/11] need to plan for end of playlist restart in all cases, at the begining --- Slim/Player/Playlist.pm | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Slim/Player/Playlist.pm b/Slim/Player/Playlist.pm index a9f7a04f431..565d7603708 100644 --- a/Slim/Player/Playlist.pm +++ b/Slim/Player/Playlist.pm @@ -190,6 +190,10 @@ sub addTracks { my $playlist = playList($client); my $maxPlaylistLength = $prefs->get('maxPlaylistLength'); + + # do we need to plan for restart of streaming? + my $restart = (Slim::Player::Source::playingSongIndex($client) == Slim::Player::Source::streamingSongIndex($client)) && + (Slim::Player::Source::playingSongIndex($client) == count($client) - 1); # How many tracks might we need to remove to make space? my $need = $maxPlaylistLength ? (scalar @{$playlist} + scalar @{$tracksRef}) - $maxPlaylistLength : 0; @@ -245,8 +249,6 @@ sub addTracks { }); } - my $playingIndex = Slim::Player::Source::playingSongIndex($client)+1; - my $streamingIndex = Slim::Player::Source::streamingSongIndex($client)+1; if ($insert) { _insert_done($client, $canAdd); @@ -254,8 +256,7 @@ sub addTracks { # if we are adding a track while we are playing the last one, might # need to relaunch the process if it has been streamed fully. - if (($playingIndex == $streamingIndex) && - ($streamingIndex == count($client) - 1)) { + if ($restart) { $log->info("adding track while playing last, check if streaming needs relaunch"); $client->controller->nextIfStreamed($client); } From 5974b6159c8e525eba57b34359f90804383a3aef Mon Sep 17 00:00:00 2001 From: philippe44 Date: Mon, 1 Jan 2024 01:11:11 -0800 Subject: [PATCH 11/11] can flush from IDLE (streaming) because streaming track might be completed as well --- Slim/Player/StreamingController.pm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Slim/Player/StreamingController.pm b/Slim/Player/StreamingController.pm index 8e16085c0e9..c8aa6bfd9e3 100644 --- a/Slim/Player/StreamingController.pm +++ b/Slim/Player/StreamingController.pm @@ -147,8 +147,8 @@ Flush => [ [ \&_Invalid, \&_BadState, \&_BadState, \&_Invalid], # STOPPED [ \&_BadState, \&_Invalid, \&_Invalid, \&_BadState], # BUFFERING [ \&_BadState, \&_Invalid, \&_Invalid, \&_BadState], # WAITING_TO_SYNC - [ \&_Invalid, \&_FlushGetNext,\&_FlushGetNext,\&_FlushGetNext], # PLAYING - [ \&_Invalid, \&_FlushGetNext,\&_FlushGetNext,\&_FlushGetNext], # PAUSED + [ \&_FlushGetNext,\&_FlushGetNext,\&_FlushGetNext,\&_FlushGetNext], # PLAYING + [ \&_FlushGetNext,\&_FlushGetNext,\&_FlushGetNext,\&_FlushGetNext], # PAUSED ], Skip => [ [ \&_StopGetNext, \&_BadState, \&_BadState, \&_NoOp], # STOPPED