Playlist.cxx 7.38 KB
Newer Older
1
/*
Max Kellermann's avatar
Max Kellermann committed
2
 * Copyright (C) 2003-2015 The Music Player Daemon Project
3
 * http://www.musicpd.org
Warren Dukes's avatar
Warren Dukes committed
4 5 6 7 8 9 10 11 12 13
 *
 * 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.
Warren Dukes's avatar
Warren Dukes committed
18 19
 */

20
#include "config.h"
21
#include "Playlist.hxx"
22
#include "PlaylistError.hxx"
23
#include "player/Control.hxx"
24
#include "DetachedSong.hxx"
Max Kellermann's avatar
Max Kellermann committed
25
#include "Idle.hxx"
26
#include "Log.hxx"
Warren Dukes's avatar
Warren Dukes committed
27

Max Kellermann's avatar
Max Kellermann committed
28
#include <assert.h>
29

30
void
31
playlist::TagModified(DetachedSong &&song)
32
{
33
	if (!playing)
34 35
		return;

36
	assert(current >= 0);
37

38 39 40
	DetachedSong &current_song = queue.GetOrder(current);
	if (song.IsSame(current_song))
		current_song.MoveTagFrom(std::move(song));
41

42
	queue.ModifyAtOrder(current);
43
	queue.IncrementVersion();
44
	idle_add(IDLE_PLAYLIST);
45 46
}

47 48 49
inline void
playlist::QueueSongOrder(PlayerControl &pc, unsigned order)

Avuton Olrich's avatar
Avuton Olrich committed
50
{
51
	assert(queue.IsValidOrder(order));
52

53
	queued = order;
54

55
	const DetachedSong &song = queue.GetOrder(order);
56

57
	FormatDebug(playlist_domain, "queue song %i:\"%s\"",
58
		    queued, song.GetURI());
59

60
	pc.EnqueueSong(new DetachedSong(song));
61
}
62

63 64 65 66
void
playlist::SongStarted()
{
	assert(current >= 0);
67 68 69 70

	/* reset a song's "priority" when playback starts */
	if (queue.SetPriority(queue.OrderToPosition(current), 0, -1, false))
		OnModified();
71 72
}

73 74
inline void
playlist::QueuedSongStarted(PlayerControl &pc)
Avuton Olrich's avatar
Avuton Olrich committed
75
{
76
	assert(pc.next_song == nullptr);
77
	assert(queued >= -1);
78
	assert(current >= 0);
79

80 81
	/* queued song has started: copy queued to current,
	   and notify the clients */
82

83 84 85
	const int old_current = current;
	current = queued;
	queued = -1;
86

87 88
	if (queue.consume)
		DeleteOrder(pc, old_current);
89 90

	idle_add(IDLE_PLAYER);
91 92

	SongStarted();
Warren Dukes's avatar
Warren Dukes committed
93 94
}

95
const DetachedSong *
96
playlist::GetQueuedSong() const
Avuton Olrich's avatar
Avuton Olrich committed
97
{
98
	return playing && queued >= 0
99
		? &queue.GetOrder(queued)
100
		: nullptr;
101 102
}

103
void
104
playlist::UpdateQueuedSong(PlayerControl &pc, const DetachedSong *prev)
105
{
106
	if (!playing)
107 108
		return;

109 110 111 112 113 114
	if (prev == nullptr && bulk_edit)
		/* postponed until CommitBulk() to avoid always
		   queueing the first song that is being added (in
		   random mode) */
		return;

115
	assert(!queue.IsEmpty());
116
	assert((queued < 0) == (prev == nullptr));
117

118 119
	const int next_order = current >= 0
		? queue.GetNextOrder(current)
120 121
		: 0;

122
	if (next_order == 0 && queue.random && !queue.single) {
123 124 125
		/* shuffle the song order again, so we get a different
		   order each time the playlist is played
		   completely */
126 127
		const unsigned current_position =
			queue.OrderToPosition(current);
128

129
		queue.ShuffleOrder();
130

131
		/* make sure that the current still points to
132 133
		   the current song, after the song order has been
		   shuffled */
134
		current = queue.PositionToOrder(current_position);
135 136
	}

137
	const DetachedSong *const next_song = next_order >= 0
138
		? &queue.GetOrder(next_order)
139
		: nullptr;
140

141
	if (prev != nullptr && next_song != prev) {
142
		/* clear the currently queued song */
143
		pc.Cancel();
144
		queued = -1;
145
	}
Warren Dukes's avatar
Warren Dukes committed
146

147 148
	if (next_order >= 0) {
		if (next_song != prev)
149
			QueueSongOrder(pc, next_order);
150
		else
151
			queued = next_order;
152
	}
153 154
}

155
void
156
playlist::PlayOrder(PlayerControl &pc, int order)
Avuton Olrich's avatar
Avuton Olrich committed
157
{
158 159
	playing = true;
	queued = -1;
Eric Wong's avatar
Eric Wong committed
160

161
	const DetachedSong &song = queue.GetOrder(order);
Warren Dukes's avatar
Warren Dukes committed
162

163
	FormatDebug(playlist_domain, "play %i:\"%s\"", order, song.GetURI());
Warren Dukes's avatar
Warren Dukes committed
164

165
	pc.Play(new DetachedSong(song));
166
	current = order;
167 168

	SongStarted();
Warren Dukes's avatar
Warren Dukes committed
169 170
}

171
void
172
playlist::SyncWithPlayer(PlayerControl &pc)
Avuton Olrich's avatar
Avuton Olrich committed
173
{
174
	if (!playing)
175 176
		/* this event has reached us out of sync: we aren't
		   playing anymore; ignore the event */
Avuton Olrich's avatar
Avuton Olrich committed
177
		return;
Warren Dukes's avatar
Warren Dukes committed
178

179
	pc.Lock();
180
	const PlayerState pc_state = pc.GetState();
181
	const DetachedSong *pc_next_song = pc.next_song;
182
	pc.Unlock();
183

184
	if (pc_state == PlayerState::STOP)
185 186 187 188
		/* the player thread has stopped: check if playback
		   should be restarted with the next song.  That can
		   happen if the playlist isn't filling the queue fast
		   enough */
189
		ResumePlayback(pc);
190
	else {
191 192
		/* check if the player thread has already started
		   playing the queued song */
193
		if (pc_next_song == nullptr && queued != -1)
194
			QueuedSongStarted(pc);
195

196
		pc.Lock();
197
		pc_next_song = pc.next_song;
198
		pc.Unlock();
199

200 201
		/* make sure the queued song is always set (if
		   possible) */
202 203
		if (pc_next_song == nullptr && queued < 0)
			UpdateQueuedSong(pc, nullptr);
204
	}
Warren Dukes's avatar
Warren Dukes committed
205 206
}

207 208
inline void
playlist::ResumePlayback(PlayerControl &pc)
Avuton Olrich's avatar
Avuton Olrich committed
209
{
210
	assert(playing);
211
	assert(pc.GetState() == PlayerState::STOP);
Warren Dukes's avatar
Warren Dukes committed
212

213
	const auto error = pc.GetErrorType();
214
	if (error == PlayerError::NONE)
215
		error_count = 0;
216
	else
217
		++error_count;
218

219
	if ((stop_on_error && error != PlayerError::NONE) ||
220
	    error == PlayerError::OUTPUT ||
221
	    error_count >= queue.GetLength())
222 223
		/* too many errors, or critical error: stop
		   playback */
224
		Stop(pc);
225
	else
226
		/* continue playback at the next song */
227
		PlayNext(pc);
228 229
}

230
void
231
playlist::SetRepeat(PlayerControl &pc, bool status)
Avuton Olrich's avatar
Avuton Olrich committed
232
{
233
	if (status == queue.repeat)
234 235
		return;

236
	queue.repeat = status;
237

238
	pc.SetBorderPause(queue.single && !queue.repeat);
239

240 241
	/* if the last song is currently being played, the "next song"
	   might change when repeat mode is toggled */
242
	UpdateQueuedSong(pc, GetQueuedSong());
243

244
	idle_add(IDLE_OPTIONS);
Warren Dukes's avatar
Warren Dukes committed
245 246
}

247
static void
248
playlist_order(playlist &playlist)
Avuton Olrich's avatar
Avuton Olrich committed
249
{
250
	if (playlist.current >= 0)
251
		/* update playlist.current, order==position now */
252
		playlist.current = playlist.queue.OrderToPosition(playlist.current);
Warren Dukes's avatar
Warren Dukes committed
253

254
	playlist.queue.RestoreOrder();
Warren Dukes's avatar
Warren Dukes committed
255 256
}

257
void
258
playlist::SetSingle(PlayerControl &pc, bool status)
259
{
260
	if (status == queue.single)
261 262
		return;

263
	queue.single = status;
264

265
	pc.SetBorderPause(queue.single && !queue.repeat);
266 267

	/* if the last song is currently being played, the "next song"
268
	   might change when single mode is toggled */
269
	UpdateQueuedSong(pc, GetQueuedSong());
270 271 272 273

	idle_add(IDLE_OPTIONS);
}

274
void
275
playlist::SetConsume(bool status)
276
{
277
	if (status == queue.consume)
278 279
		return;

280
	queue.consume = status;
281 282 283
	idle_add(IDLE_OPTIONS);
}

284
void
285
playlist::SetRandom(PlayerControl &pc, bool status)
Avuton Olrich's avatar
Avuton Olrich committed
286
{
287
	if (status == queue.random)
288
		return;
Warren Dukes's avatar
Warren Dukes committed
289

290
	const DetachedSong *const queued_song = GetQueuedSong();
291

292
	queue.random = status;
Warren Dukes's avatar
Warren Dukes committed
293

294 295
	if (queue.random) {
		/* shuffle the queue order, but preserve current */
296

297 298 299
		const int current_position = playing
			? GetCurrentPosition()
			: -1;
300

301
		queue.ShuffleOrder();
302 303

		if (current_position >= 0) {
304 305 306
			/* make sure the current song is the first in
			   the order list, so the whole rest of the
			   playlist is played after that */
307
			unsigned current_order =
308 309 310
				queue.PositionToOrder(current_position);
			queue.SwapOrders(0, current_order);
			current = 0;
311
		} else
312
			current = -1;
313
	} else
314
		playlist_order(*this);
315

316
	UpdateQueuedSong(pc, queued_song);
317

318
	idle_add(IDLE_OPTIONS);
Warren Dukes's avatar
Warren Dukes committed
319 320
}

321
int
322
playlist::GetCurrentPosition() const
Avuton Olrich's avatar
Avuton Olrich committed
323
{
324 325 326
	return current >= 0
		? queue.OrderToPosition(current)
		: -1;
327 328
}

329
int
330
playlist::GetNextPosition() const
Avuton Olrich's avatar
Avuton Olrich committed
331
{
332 333
	if (current < 0)
		return -1;
334

335 336 337 338 339 340
	if (queue.single && queue.repeat)
		return queue.OrderToPosition(current);
	else if (queue.IsValidOrder(current + 1))
		return queue.OrderToPosition(current + 1);
	else if (queue.repeat)
		return queue.OrderToPosition(0);
341

342
	return -1;
343
}