Playlist.cxx 7.62 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 "PlayerControl.hxx"
23
#include "song.h"
Max Kellermann's avatar
Max Kellermann committed
24
#include "Idle.hxx"
Warren Dukes's avatar
Warren Dukes committed
25

26 27
#include <glib.h>

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

30 31 32
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "playlist"

33
void
34
playlist::FullIncrementVersions()
Avuton Olrich's avatar
Avuton Olrich committed
35
{
36
	queue.ModifyAll();
37
	idle_add(IDLE_PLAYLIST);
Warren Dukes's avatar
Warren Dukes committed
38 39
}

40
void
41
playlist::TagChanged()
42
{
43
	if (!playing)
44 45
		return;

46
	assert(current >= 0);
47

48
	queue.ModifyAtOrder(current);
49
	idle_add(IDLE_PLAYLIST);
50 51
}

52 53 54
/**
 * Queue a song, addressed by its order number.
 */
55
static void
56 57
playlist_queue_song_order(struct playlist *playlist, struct player_control *pc,
			  unsigned order)
Avuton Olrich's avatar
Avuton Olrich committed
58
{
59
	char *uri;
60

61
	assert(playlist->queue.IsValidOrder(order));
62

63
	playlist->queued = order;
64

65
	struct song *song =
66
		song_dup_detached(playlist->queue.GetOrder(order));
67

68
	uri = song_get_uri(song);
69
	g_debug("queue song %i:\"%s\"", playlist->queued, uri);
70 71
	g_free(uri);

72
	pc_enqueue_song(pc, song);
73
}
74

75
/**
76
 * Called if the player thread has started playing the "queued" song.
77
 */
78
static void
79
playlist_song_started(struct playlist *playlist, struct player_control *pc)
Avuton Olrich's avatar
Avuton Olrich committed
80
{
81
	assert(pc->next_song == NULL);
82
	assert(playlist->queued >= -1);
83

84 85
	/* queued song has started: copy queued to current,
	   and notify the clients */
86

87 88 89
	int current = playlist->current;
	playlist->current = playlist->queued;
	playlist->queued = -1;
90

91
	if(playlist->queue.consume)
92
		playlist->DeleteOrder(*pc, current);
93 94

	idle_add(IDLE_PLAYER);
Warren Dukes's avatar
Warren Dukes committed
95 96
}

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

105
void
106
playlist::UpdateQueuedSong(player_control &pc, const song *prev)
107
{
108
	if (!playing)
109 110
		return;

111 112
	assert(!queue.IsEmpty());
	assert((queued < 0) == (prev == NULL));
113

114 115
	const int next_order = current >= 0
		? queue.GetNextOrder(current)
116 117
		: 0;

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

125
		queue.ShuffleOrder();
126

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

133 134 135
	const struct song *const next_song = next_order >= 0
		? queue.GetOrder(next_order)
		: nullptr;
136 137 138

	if (prev != NULL && next_song != prev) {
		/* clear the currently queued song */
139 140
		pc_cancel(&pc);
		queued = -1;
141
	}
Warren Dukes's avatar
Warren Dukes committed
142

143 144
	if (next_order >= 0) {
		if (next_song != prev)
145
			playlist_queue_song_order(this, &pc, next_order);
146
		else
147
			queued = next_order;
148
	}
149 150
}

151
void
152
playlist::PlayOrder(player_control &pc, int order)
Avuton Olrich's avatar
Avuton Olrich committed
153
{
154 155
	playing = true;
	queued = -1;
Eric Wong's avatar
Eric Wong committed
156

157
	struct song *song = song_dup_detached(queue.GetOrder(order));
Warren Dukes's avatar
Warren Dukes committed
158

159 160
	char *uri = song_get_uri(song);
	g_debug("play %i:\"%s\"", order, uri);
161
	g_free(uri);
Warren Dukes's avatar
Warren Dukes committed
162

163 164
	pc_play(&pc, song);
	current = order;
Warren Dukes's avatar
Warren Dukes committed
165 166
}

167
static void
168
playlist_resume_playback(struct playlist *playlist, struct player_control *pc);
169

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

178 179 180 181
	player_lock(&pc);
	const enum player_state pc_state = pc_get_state(&pc);
	const song *pc_next_song = pc.next_song;
	player_unlock(&pc);
182 183

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

195 196 197
		player_lock(&pc);
		pc_next_song = pc.next_song;
		player_unlock(&pc);
198

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

206 207 208 209
/**
 * The player has stopped for some reason.  Check the error, and
 * decide whether to re-start playback
 */
210
static void
211
playlist_resume_playback(struct playlist *playlist, struct player_control *pc)
Avuton Olrich's avatar
Avuton Olrich committed
212
{
213
	enum player_error error;
Warren Dukes's avatar
Warren Dukes committed
214

215
	assert(playlist->playing);
216
	assert(pc_get_state(pc) == PLAYER_STATE_STOP);
Warren Dukes's avatar
Warren Dukes committed
217

218
	error = pc_get_error_type(pc);
219
	if (error == PLAYER_ERROR_NONE)
220
		playlist->error_count = 0;
221
	else
222
		++playlist->error_count;
223

224 225
	if ((playlist->stop_on_error && error != PLAYER_ERROR_NONE) ||
	    error == PLAYER_ERROR_OUTPUT ||
226
	    playlist->error_count >= playlist->queue.GetLength())
227 228
		/* too many errors, or critical error: stop
		   playback */
229
		playlist->Stop(*pc);
230
	else
231
		/* continue playback at the next song */
232
		playlist->PlayNext(*pc);
233 234
}

235
void
236
playlist::SetRepeat(player_control &pc, bool status)
Avuton Olrich's avatar
Avuton Olrich committed
237
{
238
	if (status == queue.repeat)
239 240
		return;

241
	queue.repeat = status;
242

243
	pc_set_border_pause(&pc, queue.single && !queue.repeat);
244

245 246
	/* if the last song is currently being played, the "next song"
	   might change when repeat mode is toggled */
247
	UpdateQueuedSong(pc, GetQueuedSong());
248

249
	idle_add(IDLE_OPTIONS);
Warren Dukes's avatar
Warren Dukes committed
250 251
}

252 253
static void
playlist_order(struct playlist *playlist)
Avuton Olrich's avatar
Avuton Olrich committed
254
{
255
	if (playlist->current >= 0)
256
		/* update playlist.current, order==position now */
257
		playlist->current = playlist->queue.OrderToPosition(playlist->current);
Warren Dukes's avatar
Warren Dukes committed
258

259
	playlist->queue.RestoreOrder();
Warren Dukes's avatar
Warren Dukes committed
260 261
}

262
void
263
playlist::SetSingle(player_control &pc, bool status)
264
{
265
	if (status == queue.single)
266 267
		return;

268
	queue.single = status;
269

270
	pc_set_border_pause(&pc, queue.single && !queue.repeat);
271 272

	/* if the last song is currently being played, the "next song"
273
	   might change when single mode is toggled */
274
	UpdateQueuedSong(pc, GetQueuedSong());
275 276 277 278

	idle_add(IDLE_OPTIONS);
}

279
void
280
playlist::SetConsume(bool status)
281
{
282
	if (status == queue.consume)
283 284
		return;

285
	queue.consume = status;
286 287 288
	idle_add(IDLE_OPTIONS);
}

289
void
290
playlist::SetRandom(player_control &pc, bool status)
Avuton Olrich's avatar
Avuton Olrich committed
291
{
292
	if (status == queue.random)
293
		return;
Warren Dukes's avatar
Warren Dukes committed
294

295
	const struct song *const queued_song = GetQueuedSong();
296

297
	queue.random = status;
Warren Dukes's avatar
Warren Dukes committed
298

299 300
	if (queue.random) {
		/* shuffle the queue order, but preserve current */
301

302
		const int current_position = GetCurrentPosition();
303

304
		queue.ShuffleOrder();
305 306

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

319
	UpdateQueuedSong(pc, queued_song);
320

321
	idle_add(IDLE_OPTIONS);
Warren Dukes's avatar
Warren Dukes committed
322 323
}

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

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

338 339 340 341 342 343
	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);
344

345
	return -1;
346
}