Control.cxx 6.77 KB
Newer Older
1
/*
Max Kellermann's avatar
Max Kellermann committed
2
 * Copyright 2003-2021 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 "Control.hxx"
21
#include "Outputs.hxx"
Max Kellermann's avatar
Max Kellermann committed
22
#include "Idle.hxx"
23
#include "song/DetachedSong.hxx"
24

25
#include <algorithm>
26
#include <cassert>
27

28
PlayerControl::PlayerControl(PlayerListener &_listener,
29
			     PlayerOutputs &_outputs,
30
			     InputCacheManager *_input_cache,
31
			     unsigned _buffer_chunks,
32
			     AudioFormat _configured_audio_format,
33
			     const ReplayGainConfig &_replay_gain_config) noexcept
34
	:listener(_listener), outputs(_outputs),
35
	 input_cache(_input_cache),
36
	 buffer_chunks(_buffer_chunks),
37
	 configured_audio_format(_configured_audio_format),
38
	 thread(BIND_THIS_METHOD(RunThread)),
39
	 replay_gain_config(_replay_gain_config)
40 41 42
{
}

43
PlayerControl::~PlayerControl() noexcept
44
{
45
	assert(!occupied);
46 47
}

48
bool
49 50
PlayerControl::WaitOutputConsumed(std::unique_lock<Mutex> &lock,
				  unsigned threshold) noexcept
51
{
52
	bool result = outputs.CheckPipe() < threshold;
53
	if (!result && command == PlayerCommand::NONE) {
54
		Wait(lock);
55
		result = outputs.CheckPipe() < threshold;
56 57 58 59 60
	}

	return result;
}

61
void
62
PlayerControl::Play(std::unique_ptr<DetachedSong> song)
63
{
64 65 66
	if (!thread.IsDefined())
		thread.Start();

67
	assert(song != nullptr);
68

69 70
	std::unique_lock<Mutex> lock(mutex);
	SeekLocked(lock, std::move(song), SongTime::zero());
71

72
	if (state == PlayerState::PAUSE)
73 74
		/* if the player was paused previously, we need to
		   unpause it */
75
		PauseLocked(lock);
76 77
}

78
void
79
PlayerControl::LockCancel() noexcept
80
{
81 82
	assert(thread.IsDefined());

83
	LockSynchronousCommand(PlayerCommand::CANCEL);
84
	assert(next_song == nullptr);
85
}
86

87
void
88
PlayerControl::LockStop() noexcept
89
{
90 91 92
	if (!thread.IsDefined())
		return;

93
	LockSynchronousCommand(PlayerCommand::CLOSE_AUDIO);
94
	assert(next_song == nullptr);
95 96

	idle_add(IDLE_PLAYER);
97 98
}

99
void
100
PlayerControl::LockUpdateAudio() noexcept
101
{
102 103 104
	if (!thread.IsDefined())
		return;

105
	LockSynchronousCommand(PlayerCommand::UPDATE_AUDIO);
106 107
}

108
void
109
PlayerControl::Kill() noexcept
110
{
111 112
	if (!thread.IsDefined())
		return;
113

114
	LockSynchronousCommand(PlayerCommand::EXIT);
115
	thread.Join();
116 117

	idle_add(IDLE_PLAYER);
118 119
}

120
void
121
PlayerControl::PauseLocked(std::unique_lock<Mutex> &lock) noexcept
122
{
123
	if (state != PlayerState::STOP) {
124
		SynchronousCommand(lock, PlayerCommand::PAUSE);
125 126 127 128
		idle_add(IDLE_PLAYER);
	}
}

129
void
130
PlayerControl::LockPause() noexcept
131
{
132 133
	std::unique_lock<Mutex> lock(mutex);
	PauseLocked(lock);
134 135
}

136
void
137
PlayerControl::LockSetPause(bool pause_flag) noexcept
138
{
139 140 141
	if (!thread.IsDefined())
		return;

142
	std::unique_lock<Mutex> lock(mutex);
143

144
	switch (state) {
145
	case PlayerState::STOP:
146 147
		break;

148
	case PlayerState::PLAY:
149
		if (pause_flag)
150
			PauseLocked(lock);
151
		break;
152

153
	case PlayerState::PAUSE:
154
		if (!pause_flag)
155
			PauseLocked(lock);
156 157 158 159
		break;
	}
}

160
void
161
PlayerControl::LockSetBorderPause(bool _border_pause) noexcept
162
{
163
	const std::lock_guard<Mutex> protect(mutex);
164
	border_pause = _border_pause;
165 166
}

167
PlayerStatus
168
PlayerControl::LockGetStatus() noexcept
169
{
170
	PlayerStatus status;
171

172
	std::unique_lock<Mutex> lock(mutex);
173
	if (!occupied && thread.IsDefined())
174
		SynchronousCommand(lock, PlayerCommand::REFRESH);
175

176 177
	status.state = state;

178
	if (state != PlayerState::STOP) {
179 180 181 182
		status.bit_rate = bit_rate;
		status.audio_format = audio_format;
		status.total_time = total_time;
		status.elapsed_time = elapsed_time;
183
	}
184

185
	return status;
186 187
}

188
void
189
PlayerControl::SetError(PlayerError type, std::exception_ptr &&_error) noexcept
190
{
191
	assert(type != PlayerError::NONE);
192
	assert(_error);
193

194
	error_type = type;
195
	error = std::move(_error);
196 197
}

198
void
199
PlayerControl::LockClearError() noexcept
200
{
201
	const std::lock_guard<Mutex> protect(mutex);
202
	ClearError();
203 204
}

205
void
206
PlayerControl::LockSetTaggedSong(const DetachedSong &song) noexcept
207
{
208
	const std::lock_guard<Mutex> protect(mutex);
209 210
	tagged_song.reset();
	tagged_song = std::make_unique<DetachedSong>(song);
211 212 213
}

void
214
PlayerControl::ClearTaggedSong() noexcept
215
{
216 217 218 219 220 221 222 223 224 225 226 227 228 229
	tagged_song.reset();
}

std::unique_ptr<DetachedSong>
PlayerControl::ReadTaggedSong() noexcept
{
	return std::exchange(tagged_song, nullptr);
}

std::unique_ptr<DetachedSong>
PlayerControl::LockReadTaggedSong() noexcept
{
	const std::lock_guard<Mutex> protect(mutex);
	return ReadTaggedSong();
230 231
}

232
void
233
PlayerControl::LockEnqueueSong(std::unique_ptr<DetachedSong> song) noexcept
234
{
235
	assert(thread.IsDefined());
236
	assert(song != nullptr);
237

238 239
	std::unique_lock<Mutex> lock(mutex);
	EnqueueSongLocked(lock, std::move(song));
240 241 242
}

void
243 244
PlayerControl::EnqueueSongLocked(std::unique_lock<Mutex> &lock,
				 std::unique_ptr<DetachedSong> song) noexcept
245 246 247 248 249 250
{
	assert(song != nullptr);
	assert(next_song == nullptr);

	next_song = std::move(song);
	seek_time = SongTime::zero();
251
	SynchronousCommand(lock, PlayerCommand::QUEUE);
252 253
}

254
void
255 256
PlayerControl::SeekLocked(std::unique_lock<Mutex> &lock,
			  std::unique_ptr<DetachedSong> song, SongTime t)
257
{
258
	assert(song != nullptr);
259

260 261 262 263
	/* 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 */
264
	if (next_song != nullptr)
265
		SynchronousCommand(lock, PlayerCommand::CANCEL);
266 267 268

	assert(next_song == nullptr);

269
	ClearError();
270
	next_song = std::move(song);
271
	seek_time = t;
272
	SynchronousCommand(lock, PlayerCommand::SEEK);
273

274
	assert(next_song == nullptr);
275

276 277 278
	/* the SEEK command is asynchronous; until completion, the
	   "seeking" flag is set */
	while (seeking)
279
		ClientWait(lock);
280

281
	if (error_type != PlayerError::NONE) {
282 283
		assert(error);
		std::rethrow_exception(error);
284 285
	}

286
	assert(!error);
287 288
}

289
void
290
PlayerControl::LockSeek(std::unique_ptr<DetachedSong> song, SongTime t)
291
{
292 293 294
	if (!thread.IsDefined())
		thread.Start();

295 296
	assert(song != nullptr);

297 298
	std::unique_lock<Mutex> lock(mutex);
	SeekLocked(lock, std::move(song), t);
299 300
}

301
void
302
PlayerControl::SetCrossFade(FloatDuration duration) noexcept
303
{
304
	cross_fade.duration = std::max(duration, FloatDuration::zero());
305 306

	idle_add(IDLE_OPTIONS);
307 308
}

309
void
310
PlayerControl::SetMixRampDb(float _mixramp_db) noexcept
311
{
312
	cross_fade.mixramp_db = _mixramp_db;
313 314 315 316 317

	idle_add(IDLE_OPTIONS);
}

void
318
PlayerControl::SetMixRampDelay(FloatDuration _mixramp_delay) noexcept
319
{
320
	cross_fade.mixramp_delay = _mixramp_delay;
321 322 323

	idle_add(IDLE_OPTIONS);
}