PlaylistControl.cxx 6.32 KB
Newer Older
1
/*
Max Kellermann's avatar
Max Kellermann committed
2
 * Copyright 2003-2019 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 "Playlist.hxx"
26
#include "PlaylistError.hxx"
27
#include "player/Control.hxx"
28
#include "song/DetachedSong.hxx"
29
#include "Log.hxx"
30

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

37
	assert(current >= 0);
38

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

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

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

50
		queue.ShuffleOrder();
51 52 53

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

58
unsigned
59
playlist::MoveOrderToCurrent(unsigned old_order) noexcept
60 61 62 63 64
{
	if (!queue.random)
		/* no-op because there is no order list */
		return old_order;

65 66 67 68 69 70 71 72 73 74 75 76
	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 */
77
		return queue.MoveOrderBefore(old_order, 0);
78
	}
79 80
}

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

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

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

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

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

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

115
		i = MoveOrderToCurrent(i);
116 117
	}

118 119
	stop_on_error = false;
	error_count = 0;
120

121
	PlayOrder(pc, i);
122 123
}

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

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

136
	PlayPosition(pc, song);
137 138
}

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

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

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

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

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

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

170
			queue.ShuffleOrder();
171

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

177
		PlayOrder(pc, next_order);
178 179
	}

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

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

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

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

206
	PlayOrder(pc, order);
207 208
}

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

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

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

222 223
		i = MoveOrderToCurrent(i);

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

228 229
	queued = -1;

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

237
	UpdateQueuedSong(pc, nullptr);
238 239
}

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

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

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

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

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

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

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

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

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

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

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