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