2009-02-04 20:31:22 +01:00
|
|
|
/*
|
2015-01-01 19:48:13 +01:00
|
|
|
* Copyright (C) 2003-2015 The Music Player Daemon Project
|
2009-02-04 20:31:22 +01:00
|
|
|
* http://www.musicpd.org
|
|
|
|
*
|
|
|
|
* This program is free software; you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU General Public License as published by
|
|
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
|
|
* (at your option) any later version.
|
|
|
|
*
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU General Public License for more details.
|
2009-03-13 18:43:16 +01:00
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU General Public License along
|
|
|
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
|
|
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
2009-02-04 20:31:22 +01:00
|
|
|
*/
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Functions for controlling playback on the playlist level.
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
2009-11-12 09:12:38 +01:00
|
|
|
#include "config.h"
|
2013-01-07 10:55:05 +01:00
|
|
|
#include "Playlist.hxx"
|
2013-09-27 22:31:24 +02:00
|
|
|
#include "PlaylistError.hxx"
|
2015-08-15 15:55:46 +02:00
|
|
|
#include "player/Control.hxx"
|
2014-01-07 21:39:47 +01:00
|
|
|
#include "DetachedSong.hxx"
|
2013-09-27 22:31:24 +02:00
|
|
|
#include "Log.hxx"
|
2009-02-04 22:15:31 +01:00
|
|
|
|
2009-11-03 21:08:48 +01:00
|
|
|
void
|
2013-10-28 10:12:21 +01:00
|
|
|
playlist::Stop(PlayerControl &pc)
|
2009-02-04 20:31:22 +01:00
|
|
|
{
|
2013-01-07 10:55:05 +01:00
|
|
|
if (!playing)
|
2009-02-04 20:31:22 +01:00
|
|
|
return;
|
|
|
|
|
2013-01-07 10:55:05 +01:00
|
|
|
assert(current >= 0);
|
2009-02-04 20:31:22 +01:00
|
|
|
|
2013-09-27 22:31:24 +02:00
|
|
|
FormatDebug(playlist_domain, "stop");
|
2015-11-11 16:50:57 +01:00
|
|
|
pc.LockStop();
|
2013-01-07 10:55:05 +01:00
|
|
|
queued = -1;
|
|
|
|
playing = false;
|
2009-02-04 20:31:22 +01:00
|
|
|
|
2013-01-07 10:55:05 +01:00
|
|
|
if (queue.random) {
|
2009-02-04 20:31:22 +01:00
|
|
|
/* shuffle the playlist, so the next playback will
|
|
|
|
result in a new random order */
|
|
|
|
|
2013-01-07 10:55:05 +01:00
|
|
|
unsigned current_position = queue.OrderToPosition(current);
|
2009-02-04 20:31:22 +01:00
|
|
|
|
2013-01-07 10:55:05 +01:00
|
|
|
queue.ShuffleOrder();
|
2009-02-04 20:31:22 +01:00
|
|
|
|
|
|
|
/* make sure that "current" stays valid, and the next
|
|
|
|
"play" command plays the same song again */
|
2013-01-07 10:55:05 +01:00
|
|
|
current = queue.PositionToOrder(current_position);
|
2009-02-04 20:31:22 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-10-19 19:50:54 +02:00
|
|
|
PlaylistResult
|
2013-10-28 10:12:21 +01:00
|
|
|
playlist::PlayPosition(PlayerControl &pc, int song)
|
2009-02-04 20:31:22 +01:00
|
|
|
{
|
2015-11-11 16:50:57 +01:00
|
|
|
pc.LockClearError();
|
2009-02-04 20:31:22 +01:00
|
|
|
|
2013-01-07 10:55:05 +01:00
|
|
|
unsigned i = song;
|
2009-02-04 20:31:22 +01:00
|
|
|
if (song == -1) {
|
|
|
|
/* play any song ("current" song, or the first song */
|
|
|
|
|
2013-01-07 10:55:05 +01:00
|
|
|
if (queue.IsEmpty())
|
2013-10-19 19:50:54 +02:00
|
|
|
return PlaylistResult::SUCCESS;
|
2009-02-04 20:31:22 +01:00
|
|
|
|
2013-01-07 10:55:05 +01:00
|
|
|
if (playing) {
|
2009-02-04 20:31:22 +01:00
|
|
|
/* already playing: unpause playback, just in
|
|
|
|
case it was paused, and return */
|
2015-11-11 16:50:57 +01:00
|
|
|
pc.LockSetPause(false);
|
2013-10-19 19:50:54 +02:00
|
|
|
return PlaylistResult::SUCCESS;
|
2009-02-04 20:31:22 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/* select a song: "current" song, or the first one */
|
2013-01-07 10:55:05 +01:00
|
|
|
i = current >= 0
|
|
|
|
? current
|
2009-02-04 20:31:22 +01:00
|
|
|
: 0;
|
2013-01-07 10:55:05 +01:00
|
|
|
} else if (!queue.IsValidPosition(song))
|
2013-10-19 19:50:54 +02:00
|
|
|
return PlaylistResult::BAD_RANGE;
|
2009-02-04 20:31:22 +01:00
|
|
|
|
2013-01-07 10:55:05 +01:00
|
|
|
if (queue.random) {
|
2009-02-04 20:31:22 +01:00
|
|
|
if (song >= 0)
|
|
|
|
/* "i" is currently the song position (which
|
|
|
|
would be equal to the order number in
|
|
|
|
no-random mode); convert it to a order
|
|
|
|
number, because random mode is enabled */
|
2013-01-07 10:55:05 +01:00
|
|
|
i = queue.PositionToOrder(song);
|
2009-02-04 20:31:22 +01:00
|
|
|
|
2013-01-07 10:55:05 +01:00
|
|
|
if (!playing)
|
|
|
|
current = 0;
|
2009-02-04 20:31:22 +01:00
|
|
|
|
|
|
|
/* swap the new song with the previous "current" one,
|
|
|
|
so playback continues as planned */
|
2013-01-07 10:55:05 +01:00
|
|
|
queue.SwapOrders(i, current);
|
|
|
|
i = current;
|
2009-02-04 20:31:22 +01:00
|
|
|
}
|
|
|
|
|
2013-01-07 10:55:05 +01:00
|
|
|
stop_on_error = false;
|
|
|
|
error_count = 0;
|
2009-02-04 20:31:22 +01:00
|
|
|
|
2013-01-07 10:55:05 +01:00
|
|
|
PlayOrder(pc, i);
|
2013-10-19 19:50:54 +02:00
|
|
|
return PlaylistResult::SUCCESS;
|
2009-02-04 20:31:22 +01:00
|
|
|
}
|
|
|
|
|
2013-10-19 19:50:54 +02:00
|
|
|
PlaylistResult
|
2013-10-28 10:12:21 +01:00
|
|
|
playlist::PlayId(PlayerControl &pc, int id)
|
2009-02-04 20:31:22 +01:00
|
|
|
{
|
2013-01-07 10:55:05 +01:00
|
|
|
if (id == -1)
|
|
|
|
return PlayPosition(pc, id);
|
2009-02-04 20:31:22 +01:00
|
|
|
|
2013-01-07 10:55:05 +01:00
|
|
|
int song = queue.IdToPosition(id);
|
2009-02-04 20:31:22 +01:00
|
|
|
if (song < 0)
|
2013-10-19 19:50:54 +02:00
|
|
|
return PlaylistResult::NO_SUCH_SONG;
|
2009-02-04 20:31:22 +01:00
|
|
|
|
2013-01-07 10:55:05 +01:00
|
|
|
return PlayPosition(pc, song);
|
2009-02-04 20:31:22 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2013-10-28 10:12:21 +01:00
|
|
|
playlist::PlayNext(PlayerControl &pc)
|
2009-02-04 20:31:22 +01:00
|
|
|
{
|
2013-01-07 10:55:05 +01:00
|
|
|
if (!playing)
|
2009-02-04 20:31:22 +01:00
|
|
|
return;
|
|
|
|
|
2013-01-07 10:55:05 +01:00
|
|
|
assert(!queue.IsEmpty());
|
|
|
|
assert(queue.IsValidOrder(current));
|
2009-02-04 20:31:22 +01:00
|
|
|
|
2013-01-07 10:55:05 +01:00
|
|
|
const int old_current = current;
|
|
|
|
stop_on_error = false;
|
2009-02-04 20:31:22 +01:00
|
|
|
|
|
|
|
/* determine the next song from the queue's order list */
|
|
|
|
|
2013-01-07 10:55:05 +01:00
|
|
|
const int next_order = queue.GetNextOrder(current);
|
2009-02-04 20:31:22 +01:00
|
|
|
if (next_order < 0) {
|
|
|
|
/* no song after this one: stop playback */
|
2013-01-07 10:55:05 +01:00
|
|
|
Stop(pc);
|
2009-05-28 23:23:32 +02:00
|
|
|
|
|
|
|
/* reset "current song" */
|
2013-01-07 10:55:05 +01:00
|
|
|
current = -1;
|
2009-02-04 20:31:22 +01:00
|
|
|
}
|
2009-03-30 00:01:02 +02:00
|
|
|
else
|
|
|
|
{
|
2013-01-07 10:55:05 +01:00
|
|
|
if (next_order == 0 && queue.random) {
|
2009-03-30 00:01:02 +02:00
|
|
|
/* The queue told us that the next song is the first
|
|
|
|
song. This means we are in repeat mode. Shuffle
|
|
|
|
the queue order, so this time, the user hears the
|
|
|
|
songs in a different than before */
|
2013-01-07 10:55:05 +01:00
|
|
|
assert(queue.repeat);
|
2009-03-30 00:01:02 +02:00
|
|
|
|
2013-01-07 10:55:05 +01:00
|
|
|
queue.ShuffleOrder();
|
2009-03-30 00:01:02 +02:00
|
|
|
|
2013-01-07 10:55:05 +01:00
|
|
|
/* note that current and queued are
|
2014-07-11 19:29:25 +02:00
|
|
|
now invalid, but PlayOrder() will
|
2009-03-30 00:01:02 +02:00
|
|
|
discard them anyway */
|
|
|
|
}
|
2009-02-04 20:31:22 +01:00
|
|
|
|
2013-01-07 10:55:05 +01:00
|
|
|
PlayOrder(pc, next_order);
|
2009-02-04 20:31:22 +01:00
|
|
|
}
|
|
|
|
|
2009-03-30 00:01:02 +02:00
|
|
|
/* Consume mode removes each played songs. */
|
2013-01-07 10:55:05 +01:00
|
|
|
if (queue.consume)
|
|
|
|
DeleteOrder(pc, old_current);
|
2009-02-04 20:31:22 +01:00
|
|
|
}
|
|
|
|
|
2009-11-03 21:08:48 +01:00
|
|
|
void
|
2013-10-28 10:12:21 +01:00
|
|
|
playlist::PlayPrevious(PlayerControl &pc)
|
2009-02-04 20:31:22 +01:00
|
|
|
{
|
2013-01-07 10:55:05 +01:00
|
|
|
if (!playing)
|
2009-02-04 20:31:22 +01:00
|
|
|
return;
|
|
|
|
|
2013-01-07 10:55:05 +01:00
|
|
|
assert(!queue.IsEmpty());
|
2009-02-04 20:31:22 +01:00
|
|
|
|
2013-01-07 10:55:05 +01:00
|
|
|
int order;
|
|
|
|
if (current > 0) {
|
2009-10-08 20:33:50 +02:00
|
|
|
/* play the preceding song */
|
2013-01-07 10:55:05 +01:00
|
|
|
order = current - 1;
|
|
|
|
} else if (queue.repeat) {
|
2009-10-08 20:33:50 +02:00
|
|
|
/* play the last song in "repeat" mode */
|
2013-01-07 10:55:05 +01:00
|
|
|
order = queue.GetLength() - 1;
|
2009-02-04 20:31:22 +01:00
|
|
|
} else {
|
2009-10-08 20:33:50 +02:00
|
|
|
/* re-start playing the current song if it's
|
|
|
|
the first one */
|
2013-01-07 10:55:05 +01:00
|
|
|
order = current;
|
2009-02-04 20:31:22 +01:00
|
|
|
}
|
2013-01-07 10:55:05 +01:00
|
|
|
|
|
|
|
PlayOrder(pc, order);
|
2009-02-04 20:31:22 +01:00
|
|
|
}
|
|
|
|
|
2015-11-11 19:32:32 +01:00
|
|
|
bool
|
|
|
|
playlist::SeekSongOrder(PlayerControl &pc, unsigned i, SongTime seek_time,
|
|
|
|
Error &error)
|
2009-02-04 20:31:22 +01:00
|
|
|
{
|
2014-08-31 14:23:06 +02:00
|
|
|
assert(queue.IsValidOrder(i));
|
2009-02-04 20:31:22 +01:00
|
|
|
|
2014-01-07 21:39:47 +01:00
|
|
|
const DetachedSong *queued_song = GetQueuedSong();
|
2009-02-04 20:31:22 +01:00
|
|
|
|
2015-11-11 16:50:57 +01:00
|
|
|
pc.LockClearError();
|
2013-01-07 10:55:05 +01:00
|
|
|
stop_on_error = true;
|
|
|
|
error_count = 0;
|
2009-02-04 20:31:22 +01:00
|
|
|
|
2013-01-07 10:55:05 +01:00
|
|
|
if (!playing || (unsigned)current != i) {
|
2011-07-20 11:33:51 +02:00
|
|
|
/* seeking is not within the current song - prepare
|
|
|
|
song change */
|
|
|
|
|
2013-01-07 10:55:05 +01:00
|
|
|
playing = true;
|
|
|
|
current = i;
|
2009-02-04 20:31:22 +01:00
|
|
|
|
2013-01-07 10:55:05 +01:00
|
|
|
queued_song = nullptr;
|
2009-02-04 20:31:22 +01:00
|
|
|
}
|
|
|
|
|
2015-11-11 16:50:57 +01:00
|
|
|
if (!pc.LockSeek(new DetachedSong(queue.GetOrder(i)), seek_time)) {
|
2013-01-07 10:55:05 +01:00
|
|
|
UpdateQueuedSong(pc, queued_song);
|
2009-02-10 00:17:46 +01:00
|
|
|
|
2015-11-11 19:32:32 +01:00
|
|
|
// TODO: fix error code
|
|
|
|
error.Set(playlist_domain, int(PlaylistResult::NOT_PLAYING),
|
|
|
|
"Decoder failed to seek");
|
|
|
|
return false;
|
2009-02-10 00:17:46 +01:00
|
|
|
}
|
|
|
|
|
2013-01-07 10:55:05 +01:00
|
|
|
queued = -1;
|
2013-10-02 08:13:28 +02:00
|
|
|
UpdateQueuedSong(pc, nullptr);
|
2009-02-04 20:31:22 +01:00
|
|
|
|
2015-11-11 19:32:32 +01:00
|
|
|
return true;
|
2009-02-04 20:31:22 +01:00
|
|
|
}
|
|
|
|
|
2015-11-11 19:32:32 +01:00
|
|
|
bool
|
2014-08-31 15:01:12 +02:00
|
|
|
playlist::SeekSongPosition(PlayerControl &pc, unsigned song,
|
2015-11-11 19:32:32 +01:00
|
|
|
SongTime seek_time,
|
|
|
|
Error &error)
|
2014-08-31 14:23:06 +02:00
|
|
|
{
|
2015-11-11 19:32:32 +01:00
|
|
|
if (!queue.IsValidPosition(song)) {
|
|
|
|
error.Set(playlist_domain, int(PlaylistResult::BAD_RANGE),
|
|
|
|
"Bad range");
|
|
|
|
return false;
|
|
|
|
}
|
2014-08-31 14:23:06 +02:00
|
|
|
|
|
|
|
unsigned i = queue.random
|
|
|
|
? queue.PositionToOrder(song)
|
|
|
|
: song;
|
|
|
|
|
2015-11-11 19:32:32 +01:00
|
|
|
return SeekSongOrder(pc, i, seek_time, error);
|
2014-08-31 14:23:06 +02:00
|
|
|
}
|
|
|
|
|
2015-11-11 19:32:32 +01:00
|
|
|
bool
|
|
|
|
playlist::SeekSongId(PlayerControl &pc, unsigned id, SongTime seek_time,
|
|
|
|
Error &error)
|
2009-02-04 20:31:22 +01:00
|
|
|
{
|
2013-01-07 10:55:05 +01:00
|
|
|
int song = queue.IdToPosition(id);
|
2015-11-11 19:32:32 +01:00
|
|
|
if (song < 0) {
|
|
|
|
error.Set(playlist_domain, int(PlaylistResult::NO_SUCH_SONG),
|
|
|
|
"No such song");
|
|
|
|
return false;
|
|
|
|
}
|
2009-02-04 20:31:22 +01:00
|
|
|
|
2015-11-11 19:32:32 +01:00
|
|
|
return SeekSongPosition(pc, song, seek_time, error);
|
2009-02-04 20:31:22 +01:00
|
|
|
}
|
2011-12-24 11:20:02 +01:00
|
|
|
|
2015-11-11 19:32:32 +01:00
|
|
|
bool
|
2014-08-27 19:11:55 +02:00
|
|
|
playlist::SeekCurrent(PlayerControl &pc,
|
2015-11-11 19:32:32 +01:00
|
|
|
SignedSongTime seek_time, bool relative,
|
|
|
|
Error &error)
|
2011-12-24 11:20:02 +01:00
|
|
|
{
|
2015-11-11 19:32:32 +01:00
|
|
|
if (!playing) {
|
|
|
|
error.Set(playlist_domain, int(PlaylistResult::NOT_PLAYING),
|
|
|
|
"Not playing");
|
|
|
|
return false;
|
|
|
|
}
|
2011-12-24 11:20:02 +01:00
|
|
|
|
|
|
|
if (relative) {
|
2015-11-11 16:50:57 +01:00
|
|
|
const auto status = pc.LockGetStatus();
|
2011-12-24 11:20:02 +01:00
|
|
|
|
2013-09-27 22:07:20 +02:00
|
|
|
if (status.state != PlayerState::PLAY &&
|
2015-11-11 19:32:32 +01:00
|
|
|
status.state != PlayerState::PAUSE) {
|
|
|
|
error.Set(playlist_domain,
|
|
|
|
int(PlaylistResult::NOT_PLAYING),
|
|
|
|
"Not playing");
|
|
|
|
return false;
|
|
|
|
}
|
2011-12-24 11:20:02 +01:00
|
|
|
|
2014-08-29 13:07:38 +02:00
|
|
|
seek_time += status.elapsed_time;
|
2014-08-27 19:11:55 +02:00
|
|
|
if (seek_time.IsNegative())
|
|
|
|
seek_time = SignedSongTime::zero();
|
2011-12-24 11:20:02 +01:00
|
|
|
}
|
|
|
|
|
2014-08-31 15:01:12 +02:00
|
|
|
if (seek_time.IsNegative())
|
|
|
|
seek_time = SignedSongTime::zero();
|
2011-12-24 11:20:02 +01:00
|
|
|
|
2015-11-11 19:32:32 +01:00
|
|
|
return SeekSongOrder(pc, current, SongTime(seek_time), error);
|
2011-12-24 11:20:02 +01:00
|
|
|
}
|