Control.cxx 5.31 KB
Newer Older
1
/*
2
 * Copyright 2003-2016 The Music Player Daemon Project
3
 * http://www.musicpd.org
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.
18 19
 */

20
#include "config.h"
21
#include "Control.hxx"
Max Kellermann's avatar
Max Kellermann committed
22
#include "Idle.hxx"
23
#include "DetachedSong.hxx"
24

25
#include <algorithm>
26

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

29 30
PlayerControl::PlayerControl(PlayerListener &_listener,
			     MultipleOutputs &_outputs,
31
			     unsigned _buffer_chunks,
32
			     unsigned _buffered_before_play)
33
	:listener(_listener), outputs(_outputs),
34
	 buffer_chunks(_buffer_chunks),
35
	 buffered_before_play(_buffered_before_play),
36 37 38
	 command(PlayerCommand::NONE),
	 state(PlayerState::STOP),
	 error_type(PlayerError::NONE),
39
	 tagged_song(nullptr),
40
	 next_song(nullptr),
41 42
	 total_play_time(0),
	 border_pause(false)
43 44 45
{
}

46
PlayerControl::~PlayerControl()
47
{
48 49
	delete next_song;
	delete tagged_song;
50 51
}

52 53
bool
PlayerControl::Play(DetachedSong *song, Error &error_r)
54
{
55
	assert(song != nullptr);
56

57
	const ScopeLock protect(mutex);
58
	bool success = SeekLocked(song, SongTime::zero(), error_r);
59

60
	if (success && state == PlayerState::PAUSE)
61 62 63
		/* if the player was paused previously, we need to
		   unpause it */
		PauseLocked();
64 65

	return success;
66 67
}

68
void
69
PlayerControl::LockCancel()
70
{
71
	LockSynchronousCommand(PlayerCommand::CANCEL);
72
	assert(next_song == nullptr);
73
}
74

75
void
76
PlayerControl::LockStop()
77
{
78
	LockSynchronousCommand(PlayerCommand::CLOSE_AUDIO);
79
	assert(next_song == nullptr);
80 81

	idle_add(IDLE_PLAYER);
82 83
}

84
void
85
PlayerControl::LockUpdateAudio()
86
{
87
	LockSynchronousCommand(PlayerCommand::UPDATE_AUDIO);
88 89
}

90
void
91
PlayerControl::Kill()
92
{
93
	assert(thread.IsDefined());
94

95
	LockSynchronousCommand(PlayerCommand::EXIT);
96
	thread.Join();
97 98

	idle_add(IDLE_PLAYER);
99 100
}

101
void
102
PlayerControl::PauseLocked()
103
{
104 105
	if (state != PlayerState::STOP) {
		SynchronousCommand(PlayerCommand::PAUSE);
106 107 108 109
		idle_add(IDLE_PLAYER);
	}
}

110
void
111
PlayerControl::LockPause()
112
{
113
	const ScopeLock protect(mutex);
114
	PauseLocked();
115 116
}

117
void
118
PlayerControl::LockSetPause(bool pause_flag)
119
{
120
	const ScopeLock protect(mutex);
121

122
	switch (state) {
123
	case PlayerState::STOP:
124 125
		break;

126
	case PlayerState::PLAY:
127
		if (pause_flag)
128
			PauseLocked();
129
		break;
130

131
	case PlayerState::PAUSE:
132
		if (!pause_flag)
133
			PauseLocked();
134 135 136 137
		break;
	}
}

138
void
139
PlayerControl::LockSetBorderPause(bool _border_pause)
140
{
141
	const ScopeLock protect(mutex);
142
	border_pause = _border_pause;
143 144
}

145
player_status
146
PlayerControl::LockGetStatus()
147
{
148
	player_status status;
149

150
	const ScopeLock protect(mutex);
151
	SynchronousCommand(PlayerCommand::REFRESH);
152

153 154
	status.state = state;

155
	if (state != PlayerState::STOP) {
156 157 158 159
		status.bit_rate = bit_rate;
		status.audio_format = audio_format;
		status.total_time = total_time;
		status.elapsed_time = elapsed_time;
160
	}
161

162
	return status;
163 164
}

165
void
166
PlayerControl::SetError(PlayerError type, Error &&_error)
167
{
168
	assert(type != PlayerError::NONE);
169
	assert(_error.IsDefined());
170

171
	error_type = type;
172
	error = std::move(_error);
173 174
}

175
void
176
PlayerControl::LockClearError()
177
{
178
	const ScopeLock protect(mutex);
179
	ClearError();
180 181
}

182
void
183
PlayerControl::LockSetTaggedSong(const DetachedSong &song)
184
{
185
	const ScopeLock protect(mutex);
186 187
	delete tagged_song;
	tagged_song = new DetachedSong(song);
188 189 190
}

void
191
PlayerControl::ClearTaggedSong()
192
{
193 194
	delete tagged_song;
	tagged_song = nullptr;
195 196
}

197
void
198
PlayerControl::LockEnqueueSong(DetachedSong *song)
199
{
200
	assert(song != nullptr);
201

202
	const ScopeLock protect(mutex);
203
	EnqueueSongLocked(song);
204 205
}

206 207
bool
PlayerControl::SeekLocked(DetachedSong *song, SongTime t, Error &error_r)
208
{
209
	assert(song != nullptr);
210

211 212 213 214
	/* to issue the SEEK command below, we need to clear the
	   "next_song" attribute with the CANCEL command */
	/* optimization TODO: if the decoder happens to decode that
	   song already, don't cancel that */
215 216 217 218 219
	if (next_song != nullptr)
		SynchronousCommand(PlayerCommand::CANCEL);

	assert(next_song == nullptr);

220
	ClearError();
221
	next_song = song;
222
	seek_time = t;
223
	SynchronousCommand(PlayerCommand::SEEK);
224

225
	assert(next_song == nullptr);
226 227 228 229 230 231 232 233 234

	if (error_type != PlayerError::NONE) {
		assert(error.IsDefined());
		error_r.Set(error);
		return false;
	}

	assert(!error.IsDefined());
	return true;
235 236 237
}

bool
238
PlayerControl::LockSeek(DetachedSong *song, SongTime t, Error &error_r)
239 240 241
{
	assert(song != nullptr);

242 243
	{
		const ScopeLock protect(mutex);
244 245
		if (!SeekLocked(song, t, error_r))
			return false;
246
	}
247

248
	idle_add(IDLE_PLAYER);
249

250
	return true;
251 252
}

253
void
254
PlayerControl::SetCrossFade(float _cross_fade_seconds)
255
{
256 257
	if (_cross_fade_seconds < 0)
		_cross_fade_seconds = 0;
258
	cross_fade.duration = _cross_fade_seconds;
259 260

	idle_add(IDLE_OPTIONS);
261 262
}

263
void
264
PlayerControl::SetMixRampDb(float _mixramp_db)
265
{
266
	cross_fade.mixramp_db = _mixramp_db;
267 268 269 270 271

	idle_add(IDLE_OPTIONS);
}

void
272
PlayerControl::SetMixRampDelay(float _mixramp_delay_seconds)
273
{
274
	cross_fade.mixramp_delay = _mixramp_delay_seconds;
275 276 277

	idle_add(IDLE_OPTIONS);
}