Playlist.cxx 8.14 KB
Newer Older
1
/*
Max Kellermann's avatar
Max Kellermann committed
2
 * Copyright 2003-2017 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 "Listener.hxx"
23
#include "PlaylistError.hxx"
24
#include "player/Control.hxx"
25
#include "DetachedSong.hxx"
26
#include "SingleMode.hxx"
27
#include "Log.hxx"
Warren Dukes's avatar
Warren Dukes committed
28

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

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

37
	assert(current >= 0);
38

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

43
	queue.ModifyAtOrder(current);
44
	OnModified();
45 46
}

47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
void
playlist::TagModified(const char *uri, const Tag &tag) noexcept
{
	bool modified = false;

	for (unsigned i = 0; i < queue.length; ++i) {
		auto &song = *queue.items[i].song;
		if (song.IsURI(uri)) {
			song.SetTag(tag);
			queue.ModifyAtPosition(i);
			modified = true;
		}
	}

	if (modified)
		OnModified();
}

65 66 67
inline void
playlist::QueueSongOrder(PlayerControl &pc, unsigned order)

Avuton Olrich's avatar
Avuton Olrich committed
68
{
69
	assert(queue.IsValidOrder(order));
70

71
	queued = order;
72

73
	const DetachedSong &song = queue.GetOrder(order);
74

75
	FormatDebug(playlist_domain, "queue song %i:\"%s\"",
76
		    queued, song.GetURI());
77

78
	pc.LockEnqueueSong(std::make_unique<DetachedSong>(song));
79
}
80

81 82 83 84
void
playlist::SongStarted()
{
	assert(current >= 0);
85 86 87 88

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

91 92
inline void
playlist::QueuedSongStarted(PlayerControl &pc)
Avuton Olrich's avatar
Avuton Olrich committed
93
{
94
	assert(pc.next_song == nullptr);
95
	assert(queued >= -1);
96
	assert(current >= 0);
97

98 99
	/* queued song has started: copy queued to current,
	   and notify the clients */
100

101 102 103
	const int old_current = current;
	current = queued;
	queued = -1;
104

105 106
	if (queue.consume)
		DeleteOrder(pc, old_current);
107

108
	listener.OnQueueSongStarted();
109 110

	SongStarted();
Warren Dukes's avatar
Warren Dukes committed
111 112
}

113
const DetachedSong *
114
playlist::GetQueuedSong() const noexcept
Avuton Olrich's avatar
Avuton Olrich committed
115
{
116
	return playing && queued >= 0
117
		? &queue.GetOrder(queued)
118
		: nullptr;
119 120
}

121
void
122
playlist::UpdateQueuedSong(PlayerControl &pc, const DetachedSong *prev)
123
{
124
	if (!playing)
125 126
		return;

127 128 129 130 131 132
	if (prev == nullptr && bulk_edit)
		/* postponed until CommitBulk() to avoid always
		   queueing the first song that is being added (in
		   random mode) */
		return;

133
	assert(!queue.IsEmpty());
134
	assert((queued < 0) == (prev == nullptr));
135

136 137
	const int next_order = current >= 0
		? queue.GetNextOrder(current)
138 139
		: 0;

140
	if (next_order == 0 && queue.random && queue.single == SingleMode::OFF) {
141 142 143
		/* shuffle the song order again, so we get a different
		   order each time the playlist is played
		   completely */
144 145
		const unsigned current_position =
			queue.OrderToPosition(current);
146

147
		queue.ShuffleOrder();
148

149
		/* make sure that the current still points to
150 151
		   the current song, after the song order has been
		   shuffled */
152
		current = queue.PositionToOrder(current_position);
153 154
	}

155
	const DetachedSong *const next_song = next_order >= 0
156
		? &queue.GetOrder(next_order)
157
		: nullptr;
158

159
	if (prev != nullptr && next_song != prev) {
160
		/* clear the currently queued song */
161
		pc.LockCancel();
162
		queued = -1;
163
	}
Warren Dukes's avatar
Warren Dukes committed
164

165 166
	if (next_order >= 0) {
		if (next_song != prev)
167
			QueueSongOrder(pc, next_order);
168
		else
169
			queued = next_order;
170
	}
171 172
}

173 174
void
playlist::PlayOrder(PlayerControl &pc, unsigned order)
Avuton Olrich's avatar
Avuton Olrich committed
175
{
176 177
	playing = true;
	queued = -1;
Eric Wong's avatar
Eric Wong committed
178

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

181
	FormatDebug(playlist_domain, "play %u:\"%s\"", order, song.GetURI());
Warren Dukes's avatar
Warren Dukes committed
182

183
	current = order;
184

185
	pc.Play(std::make_unique<DetachedSong>(song));
186

187
	SongStarted();
Warren Dukes's avatar
Warren Dukes committed
188 189
}

190
void
191
playlist::SyncWithPlayer(PlayerControl &pc)
Avuton Olrich's avatar
Avuton Olrich committed
192
{
193
	if (!playing)
194 195
		/* this event has reached us out of sync: we aren't
		   playing anymore; ignore the event */
Avuton Olrich's avatar
Avuton Olrich committed
196
		return;
Warren Dukes's avatar
Warren Dukes committed
197

198
	pc.Lock();
199
	const PlayerState pc_state = pc.GetState();
200
	bool pc_has_next_song = pc.next_song != nullptr;
201
	pc.Unlock();
202

203
	if (pc_state == PlayerState::STOP)
204 205 206 207
		/* 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 */
208
		ResumePlayback(pc);
209
	else {
210 211
		/* check if the player thread has already started
		   playing the queued song */
212
		if (!pc_has_next_song && queued != -1)
213
			QueuedSongStarted(pc);
214

215
		pc.Lock();
216
		pc_has_next_song = pc.next_song != nullptr;
217
		pc.Unlock();
218

219 220
		/* make sure the queued song is always set (if
		   possible) */
221
		if (!pc_has_next_song && queued < 0)
222
			UpdateQueuedSong(pc, nullptr);
223
	}
Warren Dukes's avatar
Warren Dukes committed
224 225
}

226 227
inline void
playlist::ResumePlayback(PlayerControl &pc)
Avuton Olrich's avatar
Avuton Olrich committed
228
{
229
	assert(playing);
230
	assert(pc.GetState() == PlayerState::STOP);
Warren Dukes's avatar
Warren Dukes committed
231

232
	const auto error = pc.GetErrorType();
233
	if (error == PlayerError::NONE)
234
		error_count = 0;
235
	else
236
		++error_count;
237

238
	if ((stop_on_error && error != PlayerError::NONE) ||
239
	    error == PlayerError::OUTPUT ||
240
	    error_count >= queue.GetLength())
241 242
		/* too many errors, or critical error: stop
		   playback */
243
		Stop(pc);
244
	else
245
		/* continue playback at the next song */
246 247 248 249 250
		try {
			PlayNext(pc);
		} catch (...) {
			/* TODO: log error? */
		}
251 252
}

253
void
254
playlist::SetRepeat(PlayerControl &pc, bool status)
Avuton Olrich's avatar
Avuton Olrich committed
255
{
256
	if (status == queue.repeat)
257 258
		return;

259
	queue.repeat = status;
260

261
	pc.LockSetBorderPause(queue.single != SingleMode::OFF && !queue.repeat);
262

263 264
	/* if the last song is currently being played, the "next song"
	   might change when repeat mode is toggled */
265
	UpdateQueuedSong(pc, GetQueuedSong());
266

267
	listener.OnQueueOptionsChanged();
Warren Dukes's avatar
Warren Dukes committed
268 269
}

270
static void
271
playlist_order(playlist &playlist)
Avuton Olrich's avatar
Avuton Olrich committed
272
{
273
	if (playlist.current >= 0)
274
		/* update playlist.current, order==position now */
275
		playlist.current = playlist.queue.OrderToPosition(playlist.current);
Warren Dukes's avatar
Warren Dukes committed
276

277
	playlist.queue.RestoreOrder();
Warren Dukes's avatar
Warren Dukes committed
278 279
}

280
void
281
playlist::SetSingle(PlayerControl &pc, SingleMode status)
282
{
283
	if (status == queue.single)
284 285
		return;

286
	queue.single = status;
287

288 289

	pc.LockSetBorderPause(queue.single != SingleMode::OFF && !queue.repeat);
290 291

	/* if the last song is currently being played, the "next song"
292
	   might change when single mode is toggled */
293
	UpdateQueuedSong(pc, GetQueuedSong());
294

295
	listener.OnQueueOptionsChanged();
296 297
}

298
void
299
playlist::SetConsume(bool status)
300
{
301
	if (status == queue.consume)
302 303
		return;

304
	queue.consume = status;
305
	listener.OnQueueOptionsChanged();
306 307
}

308
void
309
playlist::SetRandom(PlayerControl &pc, bool status)
Avuton Olrich's avatar
Avuton Olrich committed
310
{
311
	if (status == queue.random)
312
		return;
Warren Dukes's avatar
Warren Dukes committed
313

314
	const DetachedSong *const queued_song = GetQueuedSong();
315

316
	queue.random = status;
Warren Dukes's avatar
Warren Dukes committed
317

318 319
	if (queue.random) {
		/* shuffle the queue order, but preserve current */
320

321 322 323
		const int current_position = playing
			? GetCurrentPosition()
			: -1;
324

325
		queue.ShuffleOrder();
326 327

		if (current_position >= 0) {
328 329 330
			/* make sure the current song is the first in
			   the order list, so the whole rest of the
			   playlist is played after that */
331
			unsigned current_order =
332
				queue.PositionToOrder(current_position);
333
			current = queue.MoveOrder(current_order, 0);
334
		} else
335
			current = -1;
336
	} else
337
		playlist_order(*this);
338

339
	UpdateQueuedSong(pc, queued_song);
340

341
	listener.OnQueueOptionsChanged();
Warren Dukes's avatar
Warren Dukes committed
342 343
}

344
int
345
playlist::GetCurrentPosition() const noexcept
Avuton Olrich's avatar
Avuton Olrich committed
346
{
347 348 349
	return current >= 0
		? queue.OrderToPosition(current)
		: -1;
350 351
}

352
int
353
playlist::GetNextPosition() const noexcept
Avuton Olrich's avatar
Avuton Olrich committed
354
{
355 356
	if (current < 0)
		return -1;
357

358
	if (queue.single != SingleMode::OFF && queue.repeat)
359 360 361 362 363
		return queue.OrderToPosition(current);
	else if (queue.IsValidOrder(current + 1))
		return queue.OrderToPosition(current + 1);
	else if (queue.repeat)
		return queue.OrderToPosition(0);
364

365
	return -1;
366
}
367 368 369 370 371 372 373 374 375 376 377

void
playlist::BorderPause(PlayerControl &pc)
{
	if (queue.single == SingleMode::ONE_SHOT) {
		queue.single = SingleMode::OFF;
		pc.LockSetBorderPause(false);

		listener.OnQueueOptionsChanged();
	}
}