Playlist.cxx 7.56 KB
Newer Older
1
/*
2
 * Copyright (C) 2003-2013 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 "Song.hxx"
25
#include "tag/Tag.hxx"
Max Kellermann's avatar
Max Kellermann committed
26
#include "Idle.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(Song &&song)
33
{
34
	if (!playing || song.tag == nullptr)
35 36
		return;

37
	assert(current >= 0);
38

39 40 41 42
	Song &current_song = queue.GetOrder(current);
	if (SongEquals(song, current_song))
		current_song.ReplaceTag(std::move(*song.tag));

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

48 49 50
/**
 * Queue a song, addressed by its order number.
 */
51
static void
52
playlist_queue_song_order(playlist &playlist, PlayerControl &pc,
53
			  unsigned order)
Avuton Olrich's avatar
Avuton Olrich committed
54
{
55
	assert(playlist.queue.IsValidOrder(order));
56

57
	playlist.queued = order;
58

59
	Song *song = playlist.queue.GetOrder(order).DupDetached();
60

61 62 63
	{
		const auto uri = song->GetURI();
		FormatDebug(playlist_domain, "queue song %i:\"%s\"",
64
			    playlist.queued, uri.c_str());
65
	}
66

67
	pc.EnqueueSong(song);
68
}
69

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

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

82 83 84
	int current = playlist.current;
	playlist.current = playlist.queued;
	playlist.queued = -1;
85

86 87
	if(playlist.queue.consume)
		playlist.DeleteOrder(pc, current);
88 89

	idle_add(IDLE_PLAYER);
Warren Dukes's avatar
Warren Dukes committed
90 91
}

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

100
void
101
playlist::UpdateQueuedSong(PlayerControl &pc, const Song *prev)
102
{
103
	if (!playing)
104 105
		return;

106
	assert(!queue.IsEmpty());
107
	assert((queued < 0) == (prev == nullptr));
108

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

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

120
		queue.ShuffleOrder();
121

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

128
	const Song *const next_song = next_order >= 0
129
		? &queue.GetOrder(next_order)
130
		: nullptr;
131

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

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

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

152
	Song *song = queue.GetOrder(order).DupDetached();
Warren Dukes's avatar
Warren Dukes committed
153

154 155 156 157 158
	{
		const auto uri = song->GetURI();
		FormatDebug(playlist_domain, "play %i:\"%s\"",
			    order, uri.c_str());
	}
Warren Dukes's avatar
Warren Dukes committed
159

160
	pc.Play(song);
161
	current = order;
Warren Dukes's avatar
Warren Dukes committed
162 163
}

164
static void
165
playlist_resume_playback(playlist &playlist, PlayerControl &pc);
166

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

175
	pc.Lock();
176
	const PlayerState pc_state = pc.GetState();
177
	const Song *pc_next_song = pc.next_song;
178
	pc.Unlock();
179

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

192
		pc.Lock();
193
		pc_next_song = pc.next_song;
194
		pc.Unlock();
195

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

203 204 205 206
/**
 * The player has stopped for some reason.  Check the error, and
 * decide whether to re-start playback
 */
207
static void
208
playlist_resume_playback(playlist &playlist, PlayerControl &pc)
Avuton Olrich's avatar
Avuton Olrich committed
209
{
210 211
	assert(playlist.playing);
	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
		playlist.error_count = 0;
216
	else
217
		++playlist.error_count;
218

219
	if ((playlist.stop_on_error && error != PlayerError::NONE) ||
220
	    error == PlayerError::OUTPUT ||
221
	    playlist.error_count >= playlist.queue.GetLength())
222 223
		/* too many errors, or critical error: stop
		   playback */
224
		playlist.Stop(pc);
225
	else
226
		/* continue playback at the next song */
227
		playlist.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 Song *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
		const int current_position = GetCurrentPosition();
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
}