# Returns a hash reference mapping filenames to an array reference listing
# the queue IDs for that file in the current play queue.
sub get_tracks_in_play_queue {
- my %matches;
- my $entry;
+ my (%matches, %idmap, $entry);
+ my $pos = -1;
MPD::exec("playlistinfo");
while (<$sock>) {
if (/^(\w+): (.*)$/) {
if ($1 eq "file") {
$entry = $matches{$2} //= [];
+ $pos++;
} elsif ($1 eq "Id") {
push @$entry, $2;
+ $idmap{$2} = $pos;
}
}
}
- return \%matches;
+ return (\%matches, \%idmap);
}
# Given an MPD playlist name, returns a reference to an array containing
# Retrieve the current play queue and target play queue.
MPD::run("tagtypes", "clear");
-my $current = get_tracks_in_play_queue();
+my ($current, $idmap) = get_tracks_in_play_queue();
my $target = get_playlist_files($ARGV[0]);
+my $end_position = (keys %$current);
+my $num_added = 0;
+my $add_start;
+
MPD::exec("command_list_begin");
for (my $i = 0; $i < @$target; $i++) {
my $f = $target->[$i];
my $id = shift @{ $current->{$f} };
+ if (defined $id and defined $add_start) {
+ my $add_position = $end_position;
+
+ MPD::exec("load", $ARGV[0], "$add_start:$i");
+ $end_position += $i - $add_start;
+ MPD::exec("move", "$add_position:$end_position", "$add_start");
+
+ undef $add_start;
+ }
+
if (defined $id) {
- MPD::exec("moveid", $id, $i);
+ # Try not to move tracks already in the right place.
+ MPD::exec("moveid", $id, $i)
+ if ($i != $idmap->{$id} + $num_added);
} else {
- MPD::exec("addid", $f, $i);
+ $add_start //= $i;
+ $num_added++;
}
}
-# Remove any tracks left from the old play queue.
-MPD::exec("deleteid", $_) foreach (map { @$_ } values %$current);
+# Now all unwanted tracks from the original playqueue have been moved to the
+# end and can be deleted all at once.
+my $rem = ($add_start // @$target) - @$target;
+MPD::exec("delete", @$target - $rem . ":") if map { @$_ } values %$current;
+MPD::exec("load", $ARGV[0], "$add_start:") if defined $add_start;
MPD::run("command_list_end");