PlaylistControl.cxx 6.19 KB
Newer Older
1
/*
Max Kellermann's avatar
Max Kellermann committed
2
 * Copyright (C) 2003-2015 The Music Player Daemon Project
3 4 5 6 7 8 9 10 11 12 13
 * 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.
14 15 16 17
 *
 * 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.
18 19 20 21 22 23 24
 */

/*
 * Functions for controlling playback on the playlist level.
 *
 */

25
#include "config.h"
26
#include "Playlist.hxx"
27
#include "PlaylistError.hxx"
28
#include "player/Control.hxx"
29
#include "DetachedSong.hxx"
30
#include "Log.hxx"
31

32
void
33
playlist::Stop(PlayerControl &pc)
34
{
35
	if (!playing)
36 37
		return;

38
	assert(current >= 0);
39

40
	FormatDebug(playlist_domain, "stop");
41
	pc.LockStop();
42 43
	queued = -1;
	playing = false;
44

45
	if (queue.random) {
46 47 48
		/* shuffle the playlist, so the next playback will
		   result in a new random order */

49
		unsigned current_position = queue.OrderToPosition(current);
50

51
		queue.ShuffleOrder();
52 53 54

		/* make sure that "current" stays valid, and the next
		   "play" command plays the same song again */
55
		current = queue.PositionToOrder(current_position);
56 57 58
	}
}

59
void
60
playlist::PlayPosition(PlayerControl &pc, int song)
61
{
62
	pc.LockClearError();
63

64
	unsigned i = song;
65 66 67
	if (song == -1) {
		/* play any song ("current" song, or the first song */

68
		if (queue.IsEmpty())
69
			return;
70

71
		if (playing) {
72 73
			/* already playing: unpause playback, just in
			   case it was paused, and return */
74
			pc.LockSetPause(false);
75
			return;
76 77 78
		}

		/* select a song: "current" song, or the first one */
79 80
		i = current >= 0
			? current
81
			: 0;
82
	} else if (!queue.IsValidPosition(song))
83
		throw PlaylistError::BadRange();
84

85
	if (queue.random) {
86 87 88 89 90
		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 */
91
			i = queue.PositionToOrder(song);
92

93 94
		if (!playing)
			current = 0;
95 96 97

		/* swap the new song with the previous "current" one,
		   so playback continues as planned */
98 99
		queue.SwapOrders(i, current);
		i = current;
100 101
	}

102 103
	stop_on_error = false;
	error_count = 0;
104

105
	PlayOrder(pc, i);
106 107
}

108
void
109
playlist::PlayId(PlayerControl &pc, int id)
110
{
111 112 113 114
	if (id == -1) {
		PlayPosition(pc, id);
		return;
	}
115

116
	int song = queue.IdToPosition(id);
117
	if (song < 0)
118
		throw PlaylistError::NoSuchSong();
119

120
	return PlayPosition(pc, song);
121 122 123
}

void
124
playlist::PlayNext(PlayerControl &pc)
125
{
126
	if (!playing)
127 128
		return;

129 130
	assert(!queue.IsEmpty());
	assert(queue.IsValidOrder(current));
131

132 133
	const int old_current = current;
	stop_on_error = false;
134 135 136

	/* determine the next song from the queue's order list */

137
	const int next_order = queue.GetNextOrder(current);
138 139
	if (next_order < 0) {
		/* no song after this one: stop playback */
140
		Stop(pc);
141 142

		/* reset "current song" */
143
		current = -1;
144
	}
145 146
	else
	{
147
		if (next_order == 0 && queue.random) {
148 149 150 151
			/* 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 */
152
			assert(queue.repeat);
153

154
			queue.ShuffleOrder();
155

156
			/* note that current and queued are
157
			   now invalid, but PlayOrder() will
158 159
			   discard them anyway */
		}
160

161
		PlayOrder(pc, next_order);
162 163
	}

164
	/* Consume mode removes each played songs. */
165 166
	if (queue.consume)
		DeleteOrder(pc, old_current);
167 168
}

169
void
170
playlist::PlayPrevious(PlayerControl &pc)
171
{
172
	if (!playing)
173 174
		return;

175
	assert(!queue.IsEmpty());
176

177 178
	int order;
	if (current > 0) {
179
		/* play the preceding song */
180 181
		order = current - 1;
	} else if (queue.repeat) {
182
		/* play the last song in "repeat" mode */
183
		order = queue.GetLength() - 1;
184
	} else {
185 186
		/* re-start playing the current song if it's
		   the first one */
187
		order = current;
188
	}
189 190

	PlayOrder(pc, order);
191 192
}

193 194 195
bool
playlist::SeekSongOrder(PlayerControl &pc, unsigned i, SongTime seek_time,
			Error &error)
196
{
197
	assert(queue.IsValidOrder(i));
198

199
	const DetachedSong *queued_song = GetQueuedSong();
200

201
	pc.LockClearError();
202 203
	stop_on_error = true;
	error_count = 0;
204

205
	if (!playing || (unsigned)current != i) {
206 207 208
		/* seeking is not within the current song - prepare
		   song change */

209 210
		playing = true;
		current = i;
211

212
		queued_song = nullptr;
213 214
	}

215
	if (!pc.LockSeek(new DetachedSong(queue.GetOrder(i)), seek_time, error)) {
216
		UpdateQueuedSong(pc, queued_song);
217
		return false;
218 219
	}

220
	queued = -1;
221
	UpdateQueuedSong(pc, nullptr);
222

223
	return true;
224 225
}

226
bool
Max Kellermann's avatar
Max Kellermann committed
227
playlist::SeekSongPosition(PlayerControl &pc, unsigned song,
228 229
			   SongTime seek_time,
			   Error &error)
230
{
231 232 233 234 235
	if (!queue.IsValidPosition(song)) {
		error.Set(playlist_domain, int(PlaylistResult::BAD_RANGE),
			  "Bad range");
		return false;
	}
236 237 238 239 240

	unsigned i = queue.random
		? queue.PositionToOrder(song)
		: song;

241
	return SeekSongOrder(pc, i, seek_time, error);
242 243
}

244 245 246
bool
playlist::SeekSongId(PlayerControl &pc, unsigned id, SongTime seek_time,
		     Error &error)
247
{
248
	int song = queue.IdToPosition(id);
249 250 251 252 253
	if (song < 0) {
		error.Set(playlist_domain, int(PlaylistResult::NO_SUCH_SONG),
			  "No such song");
		return false;
	}
254

255
	return SeekSongPosition(pc, song, seek_time, error);
256
}
257

258
bool
259
playlist::SeekCurrent(PlayerControl &pc,
260 261
		      SignedSongTime seek_time, bool relative,
		      Error &error)
262
{
263 264 265 266 267
	if (!playing) {
		error.Set(playlist_domain, int(PlaylistResult::NOT_PLAYING),
			  "Not playing");
		return false;
	}
268 269

	if (relative) {
270
		const auto status = pc.LockGetStatus();
271

272
		if (status.state != PlayerState::PLAY &&
273 274 275 276 277 278
		    status.state != PlayerState::PAUSE) {
			error.Set(playlist_domain,
				  int(PlaylistResult::NOT_PLAYING),
				  "Not playing");
			return false;
		}
279

280
		seek_time += status.elapsed_time;
281 282
		if (seek_time.IsNegative())
			seek_time = SignedSongTime::zero();
283 284
	}

Max Kellermann's avatar
Max Kellermann committed
285 286
	if (seek_time.IsNegative())
		seek_time = SignedSongTime::zero();
287

288
	return SeekSongOrder(pc, current, SongTime(seek_time), error);
289
}