1 /*
2  * Copyright 2003-2021 The Music Player Daemon Project
3  * http://www.musicpd.org
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License along
16  * with this program; if not, write to the Free Software Foundation, Inc.,
17  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18  */
19 
20 #include "Control.hxx"
21 #include "Outputs.hxx"
22 #include "Idle.hxx"
23 #include "song/DetachedSong.hxx"
24 
25 #include <algorithm>
26 #include <cassert>
27 
PlayerControl(PlayerListener & _listener,PlayerOutputs & _outputs,InputCacheManager * _input_cache,unsigned _buffer_chunks,AudioFormat _configured_audio_format,const ReplayGainConfig & _replay_gain_config)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 
~PlayerControl()43 PlayerControl::~PlayerControl() noexcept
44 {
45 	assert(!occupied);
46 }
47 
48 bool
WaitOutputConsumed(std::unique_lock<Mutex> & lock,unsigned threshold)49 PlayerControl::WaitOutputConsumed(std::unique_lock<Mutex> &lock,
50 				  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 	return result;
59 }
60 
61 void
Play(std::unique_ptr<DetachedSong> song)62 PlayerControl::Play(std::unique_ptr<DetachedSong> song)
63 {
64 	if (!thread.IsDefined())
65 		thread.Start();
66 
67 	assert(song != nullptr);
68 
69 	std::unique_lock<Mutex> lock(mutex);
70 	SeekLocked(lock, std::move(song), SongTime::zero());
71 
72 	if (state == PlayerState::PAUSE)
73 		/* if the player was paused previously, we need to
74 		   unpause it */
75 		PauseLocked(lock);
76 }
77 
78 void
LockCancel()79 PlayerControl::LockCancel() noexcept
80 {
81 	assert(thread.IsDefined());
82 
83 	LockSynchronousCommand(PlayerCommand::CANCEL);
84 	assert(next_song == nullptr);
85 }
86 
87 void
LockStop()88 PlayerControl::LockStop() noexcept
89 {
90 	if (!thread.IsDefined())
91 		return;
92 
93 	LockSynchronousCommand(PlayerCommand::CLOSE_AUDIO);
94 	assert(next_song == nullptr);
95 
96 	idle_add(IDLE_PLAYER);
97 }
98 
99 void
LockUpdateAudio()100 PlayerControl::LockUpdateAudio() noexcept
101 {
102 	if (!thread.IsDefined())
103 		return;
104 
105 	LockSynchronousCommand(PlayerCommand::UPDATE_AUDIO);
106 }
107 
108 void
Kill()109 PlayerControl::Kill() noexcept
110 {
111 	if (!thread.IsDefined())
112 		return;
113 
114 	LockSynchronousCommand(PlayerCommand::EXIT);
115 	thread.Join();
116 
117 	idle_add(IDLE_PLAYER);
118 }
119 
120 void
PauseLocked(std::unique_lock<Mutex> & lock)121 PlayerControl::PauseLocked(std::unique_lock<Mutex> &lock) noexcept
122 {
123 	if (state != PlayerState::STOP) {
124 		SynchronousCommand(lock, PlayerCommand::PAUSE);
125 		idle_add(IDLE_PLAYER);
126 	}
127 }
128 
129 void
LockPause()130 PlayerControl::LockPause() noexcept
131 {
132 	std::unique_lock<Mutex> lock(mutex);
133 	PauseLocked(lock);
134 }
135 
136 void
LockSetPause(bool pause_flag)137 PlayerControl::LockSetPause(bool pause_flag) noexcept
138 {
139 	if (!thread.IsDefined())
140 		return;
141 
142 	std::unique_lock<Mutex> lock(mutex);
143 
144 	switch (state) {
145 	case PlayerState::STOP:
146 		break;
147 
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 		break;
157 	}
158 }
159 
160 void
LockSetBorderPause(bool _border_pause)161 PlayerControl::LockSetBorderPause(bool _border_pause) noexcept
162 {
163 	const std::scoped_lock<Mutex> protect(mutex);
164 	border_pause = _border_pause;
165 }
166 
167 PlayerStatus
LockGetStatus()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 	status.state = state;
177 
178 	if (state != PlayerState::STOP) {
179 		status.bit_rate = bit_rate;
180 		status.audio_format = audio_format;
181 		status.total_time = total_time;
182 		status.elapsed_time = elapsed_time;
183 	}
184 
185 	return status;
186 }
187 
188 void
SetError(PlayerError type,std::exception_ptr && _error)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
LockClearError()199 PlayerControl::LockClearError() noexcept
200 {
201 	const std::scoped_lock<Mutex> protect(mutex);
202 	ClearError();
203 }
204 
205 void
LockSetTaggedSong(const DetachedSong & song)206 PlayerControl::LockSetTaggedSong(const DetachedSong &song) noexcept
207 {
208 	const std::scoped_lock<Mutex> protect(mutex);
209 	tagged_song.reset();
210 	tagged_song = std::make_unique<DetachedSong>(song);
211 }
212 
213 void
ClearTaggedSong()214 PlayerControl::ClearTaggedSong() noexcept
215 {
216 	tagged_song.reset();
217 }
218 
219 std::unique_ptr<DetachedSong>
ReadTaggedSong()220 PlayerControl::ReadTaggedSong() noexcept
221 {
222 	return std::exchange(tagged_song, nullptr);
223 }
224 
225 std::unique_ptr<DetachedSong>
LockReadTaggedSong()226 PlayerControl::LockReadTaggedSong() noexcept
227 {
228 	const std::scoped_lock<Mutex> protect(mutex);
229 	return ReadTaggedSong();
230 }
231 
232 void
LockEnqueueSong(std::unique_ptr<DetachedSong> song)233 PlayerControl::LockEnqueueSong(std::unique_ptr<DetachedSong> song) noexcept
234 {
235 	assert(thread.IsDefined());
236 	assert(song != nullptr);
237 
238 	std::unique_lock<Mutex> lock(mutex);
239 	EnqueueSongLocked(lock, std::move(song));
240 }
241 
242 void
EnqueueSongLocked(std::unique_lock<Mutex> & lock,std::unique_ptr<DetachedSong> song)243 PlayerControl::EnqueueSongLocked(std::unique_lock<Mutex> &lock,
244 				 std::unique_ptr<DetachedSong> song) noexcept
245 {
246 	assert(song != nullptr);
247 	assert(next_song == nullptr);
248 
249 	next_song = std::move(song);
250 	seek_time = SongTime::zero();
251 	SynchronousCommand(lock, PlayerCommand::QUEUE);
252 }
253 
254 void
SeekLocked(std::unique_lock<Mutex> & lock,std::unique_ptr<DetachedSong> song,SongTime t)255 PlayerControl::SeekLocked(std::unique_lock<Mutex> &lock,
256 			  std::unique_ptr<DetachedSong> song, SongTime t)
257 {
258 	assert(song != nullptr);
259 
260 	/* to issue the SEEK command below, we need to clear the
261 	   "next_song" attribute with the CANCEL command */
262 	/* optimization TODO: if the decoder happens to decode that
263 	   song already, don't cancel that */
264 	if (next_song != nullptr)
265 		SynchronousCommand(lock, PlayerCommand::CANCEL);
266 
267 	assert(next_song == nullptr);
268 
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 	/* the SEEK command is asynchronous; until completion, the
277 	   "seeking" flag is set */
278 	while (seeking)
279 		ClientWait(lock);
280 
281 	if (error_type != PlayerError::NONE) {
282 		assert(error);
283 		std::rethrow_exception(error);
284 	}
285 
286 	assert(!error);
287 }
288 
289 void
LockSeek(std::unique_ptr<DetachedSong> song,SongTime t)290 PlayerControl::LockSeek(std::unique_ptr<DetachedSong> song, SongTime t)
291 {
292 	if (!thread.IsDefined())
293 		thread.Start();
294 
295 	assert(song != nullptr);
296 
297 	std::unique_lock<Mutex> lock(mutex);
298 	SeekLocked(lock, std::move(song), t);
299 }
300 
301 void
SetCrossFade(FloatDuration duration)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
SetMixRampDb(float _mixramp_db)310 PlayerControl::SetMixRampDb(float _mixramp_db) noexcept
311 {
312 	cross_fade.mixramp_db = _mixramp_db;
313 
314 	idle_add(IDLE_OPTIONS);
315 }
316 
317 void
SetMixRampDelay(FloatDuration _mixramp_delay)318 PlayerControl::SetMixRampDelay(FloatDuration _mixramp_delay) noexcept
319 {
320 	cross_fade.mixramp_delay = _mixramp_delay;
321 
322 	idle_add(IDLE_OPTIONS);
323 }
324