PlaylistControl.cxx 6.33 KB
Newer Older
1
/*
Max Kellermann's avatar
Max Kellermann committed
2
 * Copyright 2003-2017 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 "song/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 60 61 62 63 64 65
unsigned
playlist::MoveOrderToCurrent(unsigned old_order)
{
	if (!queue.random)
		/* no-op because there is no order list */
		return old_order;

66 67 68 69 70 71 72 73 74 75 76 77
	if (playing) {
		/* already playing: move the specified song after the
		   current one (because the current one has already
		   been playing and shall not be played again) */
		return queue.MoveOrderAfter(old_order, current);
	} else if (current >= 0) {
		/* not playing: move the specified song before the
		   current one, so it will be played eventually */
		return queue.MoveOrderBefore(old_order, current);
	} else {
		/* not playing anything: move the specified song to
		   the front */
78
		return queue.MoveOrderBefore(old_order, 0);
79
	}
80 81
}

82 83
void
playlist::PlayPosition(PlayerControl &pc, int song)
84
{
85
	pc.LockClearError();
86

87
	unsigned i = song;
88 89 90
	if (song == -1) {
		/* play any song ("current" song, or the first song */

91
		if (queue.IsEmpty())
92
			return;
93

94
		if (playing) {
95 96
			/* already playing: unpause playback, just in
			   case it was paused, and return */
97
			pc.LockSetPause(false);
98
			return;
99 100 101
		}

		/* select a song: "current" song, or the first one */
102 103
		i = current >= 0
			? current
104
			: 0;
105
	} else if (!queue.IsValidPosition(song))
106
		throw PlaylistError::BadRange();
107

108
	if (queue.random) {
109 110 111 112 113
		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 */
114
			i = queue.PositionToOrder(song);
115

116
		i = MoveOrderToCurrent(i);
117 118
	}

119 120
	stop_on_error = false;
	error_count = 0;
121

122
	PlayOrder(pc, i);
123 124
}

125 126
void
playlist::PlayId(PlayerControl &pc, int id)
127
{
128 129 130 131
	if (id == -1) {
		PlayPosition(pc, id);
		return;
	}
132

133
	int song = queue.IdToPosition(id);
134
	if (song < 0)
135
		throw PlaylistError::NoSuchSong();
136

137
	PlayPosition(pc, song);
138 139
}

140 141
void
playlist::PlayNext(PlayerControl &pc)
142
{
143 144
	if (!playing)
		throw PlaylistError::NotPlaying();
145

146 147
	assert(!queue.IsEmpty());
	assert(queue.IsValidOrder(current));
148

149 150
	const int old_current = current;
	stop_on_error = false;
151 152 153

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

154
	const int next_order = queue.GetNextOrder(current);
155 156
	if (next_order < 0) {
		/* no song after this one: stop playback */
157
		Stop(pc);
158 159

		/* reset "current song" */
160
		current = -1;
161
	}
162 163
	else
	{
164
		if (next_order == 0 && queue.random) {
165 166 167 168
			/* 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 */
169
			assert(queue.repeat);
170

171
			queue.ShuffleOrder();
172

173
			/* note that current and queued are
174
			   now invalid, but PlayOrder() will
175 176
			   discard them anyway */
		}
177

178
		PlayOrder(pc, next_order);
179 180
	}

181
	/* Consume mode removes each played songs. */
182 183
	if (queue.consume)
		DeleteOrder(pc, old_current);
184 185
}

186 187
void
playlist::PlayPrevious(PlayerControl &pc)
188
{
189 190
	if (!playing)
		throw PlaylistError::NotPlaying();
191

192
	assert(!queue.IsEmpty());
193

194 195
	int order;
	if (current > 0) {
196
		/* play the preceding song */
197 198
		order = current - 1;
	} else if (queue.repeat) {
199
		/* play the last song in "repeat" mode */
200
		order = queue.GetLength() - 1;
201
	} else {
202 203
		/* re-start playing the current song if it's
		   the first one */
204
		order = current;
205
	}
206

207
	PlayOrder(pc, order);
208 209
}

210 211
void
playlist::SeekSongOrder(PlayerControl &pc, unsigned i, SongTime seek_time)
212
{
213
	assert(queue.IsValidOrder(i));
214

215
	pc.LockClearError();
216 217
	stop_on_error = true;
	error_count = 0;
218

219
	if (!playing || (unsigned)current != i) {
220 221 222
		/* seeking is not within the current song - prepare
		   song change */

223 224
		i = MoveOrderToCurrent(i);

225 226
		playing = true;
		current = i;
227 228
	}

229 230
	queued = -1;

231
	try {
232
		pc.LockSeek(std::make_unique<DetachedSong>(queue.GetOrder(i)), seek_time);
233
	} catch (...) {
234
		UpdateQueuedSong(pc, nullptr);
235
		throw;
236 237
	}

238
	UpdateQueuedSong(pc, nullptr);
239 240
}

241
void
Max Kellermann's avatar
Max Kellermann committed
242
playlist::SeekSongPosition(PlayerControl &pc, unsigned song,
243
			   SongTime seek_time)
244
{
245 246
	if (!queue.IsValidPosition(song))
		throw PlaylistError::BadRange();
247 248 249 250 251

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

252
	SeekSongOrder(pc, i, seek_time);
253 254
}

255 256
void
playlist::SeekSongId(PlayerControl &pc, unsigned id, SongTime seek_time)
257
{
258
	int song = queue.IdToPosition(id);
259 260
	if (song < 0)
		throw PlaylistError::NoSuchSong();
261

262
	SeekSongPosition(pc, song, seek_time);
263
}
264

265
void
266
playlist::SeekCurrent(PlayerControl &pc,
267
		      SignedSongTime seek_time, bool relative)
268
{
269 270
	if (!playing)
		throw PlaylistError::NotPlaying();
271 272

	if (relative) {
273
		const auto status = pc.LockGetStatus();
274

275
		if (status.state != PlayerState::PLAY &&
276 277
		    status.state != PlayerState::PAUSE)
			throw PlaylistError::NotPlaying();
278

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

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

287
	SeekSongOrder(pc, current, SongTime(seek_time));
288
}