Playlist.cxx 7.7 KB
Newer Older
1
/*
Max Kellermann's avatar
Max Kellermann committed
2
 * Copyright (C) 2003-2014 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 "PlayerControl.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
/**
 * Queue a song, addressed by its order number.
 */
50
static void
51
playlist_queue_song_order(playlist &playlist, PlayerControl &pc,
52
			  unsigned order)
Avuton Olrich's avatar
Avuton Olrich committed
53
{
54
	assert(playlist.queue.IsValidOrder(order));
55

56
	playlist.queued = order;
57

58
	const DetachedSong &song = playlist.queue.GetOrder(order);
59

60 61
	FormatDebug(playlist_domain, "queue song %i:\"%s\"",
		    playlist.queued, song.GetURI());
62

63
	pc.EnqueueSong(new DetachedSong(song));
64
}
65

66
/**
67
 * Called if the player thread has started playing the "queued" song.
68
 */
69
static void
70
playlist_song_started(playlist &playlist, PlayerControl &pc)
Avuton Olrich's avatar
Avuton Olrich committed
71
{
72 73
	assert(pc.next_song == nullptr);
	assert(playlist.queued >= -1);
74

75 76
	/* queued song has started: copy queued to current,
	   and notify the clients */
77

78 79 80
	int current = playlist.current;
	playlist.current = playlist.queued;
	playlist.queued = -1;
81

82 83
	if(playlist.queue.consume)
		playlist.DeleteOrder(pc, current);
84 85

	idle_add(IDLE_PLAYER);
Warren Dukes's avatar
Warren Dukes committed
86 87
}

88
const DetachedSong *
89
playlist::GetQueuedSong() const
Avuton Olrich's avatar
Avuton Olrich committed
90
{
91
	return playing && queued >= 0
92
		? &queue.GetOrder(queued)
93
		: nullptr;
94 95
}

96
void
97
playlist::UpdateQueuedSong(PlayerControl &pc, const DetachedSong *prev)
98
{
99
	if (!playing)
100 101
		return;

102 103 104 105 106 107
	if (prev == nullptr && bulk_edit)
		/* postponed until CommitBulk() to avoid always
		   queueing the first song that is being added (in
		   random mode) */
		return;

108
	assert(!queue.IsEmpty());
109
	assert((queued < 0) == (prev == nullptr));
110

111 112
	const int next_order = current >= 0
		? queue.GetNextOrder(current)
113 114
		: 0;

115
	if (next_order == 0 && queue.random && !queue.single) {
116 117 118
		/* shuffle the song order again, so we get a different
		   order each time the playlist is played
		   completely */
119 120
		const unsigned current_position =
			queue.OrderToPosition(current);
121

122
		queue.ShuffleOrder();
123

124
		/* make sure that the current still points to
125 126
		   the current song, after the song order has been
		   shuffled */
127
		current = queue.PositionToOrder(current_position);
128 129
	}

130
	const DetachedSong *const next_song = next_order >= 0
131
		? &queue.GetOrder(next_order)
132
		: nullptr;
133

134
	if (prev != nullptr && next_song != prev) {
135
		/* clear the currently queued song */
136
		pc.Cancel();
137
		queued = -1;
138
	}
Warren Dukes's avatar
Warren Dukes committed
139

140 141
	if (next_order >= 0) {
		if (next_song != prev)
142
			playlist_queue_song_order(*this, pc, next_order);
143
		else
144
			queued = next_order;
145
	}
146 147
}

148
void
149
playlist::PlayOrder(PlayerControl &pc, int order)
Avuton Olrich's avatar
Avuton Olrich committed
150
{
151 152
	playing = true;
	queued = -1;
Eric Wong's avatar
Eric Wong committed
153

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

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

158
	pc.Play(new DetachedSong(song));
159
	current = order;
Warren Dukes's avatar
Warren Dukes committed
160 161
}

162
static void
163
playlist_resume_playback(playlist &playlist, PlayerControl &pc);
164

165
void
166
playlist::SyncWithPlayer(PlayerControl &pc)
Avuton Olrich's avatar
Avuton Olrich committed
167
{
168
	if (!playing)
169 170
		/* this event has reached us out of sync: we aren't
		   playing anymore; ignore the event */
Avuton Olrich's avatar
Avuton Olrich committed
171
		return;
Warren Dukes's avatar
Warren Dukes committed
172

173
	pc.Lock();
174
	const PlayerState pc_state = pc.GetState();
175
	const DetachedSong *pc_next_song = pc.next_song;
176
	pc.Unlock();
177

178
	if (pc_state == PlayerState::STOP)
179 180 181 182
		/* 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 */
183
		playlist_resume_playback(*this, pc);
184
	else {
185 186
		/* check if the player thread has already started
		   playing the queued song */
187
		if (pc_next_song == nullptr && queued != -1)
188
			playlist_song_started(*this, pc);
189

190
		pc.Lock();
191
		pc_next_song = pc.next_song;
192
		pc.Unlock();
193

194 195
		/* make sure the queued song is always set (if
		   possible) */
196 197
		if (pc_next_song == nullptr && queued < 0)
			UpdateQueuedSong(pc, nullptr);
198
	}
Warren Dukes's avatar
Warren Dukes committed
199 200
}

201 202 203 204
/**
 * The player has stopped for some reason.  Check the error, and
 * decide whether to re-start playback
 */
205
static void
206
playlist_resume_playback(playlist &playlist, PlayerControl &pc)
Avuton Olrich's avatar
Avuton Olrich committed
207
{
208 209
	assert(playlist.playing);
	assert(pc.GetState() == PlayerState::STOP);
Warren Dukes's avatar
Warren Dukes committed
210

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

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

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

234
	queue.repeat = status;
235

236
	pc.SetBorderPause(queue.single && !queue.repeat);
237

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

242
	idle_add(IDLE_OPTIONS);
Warren Dukes's avatar
Warren Dukes committed
243 244
}

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

252
	playlist.queue.RestoreOrder();
Warren Dukes's avatar
Warren Dukes committed
253 254
}

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

261
	queue.single = status;
262

263
	pc.SetBorderPause(queue.single && !queue.repeat);
264 265

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

	idle_add(IDLE_OPTIONS);
}

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

278
	queue.consume = status;
279 280 281
	idle_add(IDLE_OPTIONS);
}

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

288
	const DetachedSong *const queued_song = GetQueuedSong();
289

290
	queue.random = status;
Warren Dukes's avatar
Warren Dukes committed
291

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

295 296 297
		const int current_position = playing
			? GetCurrentPosition()
			: -1;
298

299
		queue.ShuffleOrder();
300 301

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

314
	UpdateQueuedSong(pc, queued_song);
315

316
	idle_add(IDLE_OPTIONS);
Warren Dukes's avatar
Warren Dukes committed
317 318
}

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

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

333 334 335 336 337 338
	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);
339

340
	return -1;
341
}