Control.hxx 13.2 KB
Newer Older
1
/*
Max Kellermann's avatar
Max Kellermann committed
2
 * Copyright 2003-2017 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 21
#ifndef MPD_PLAYER_CONTROL_HXX
#define MPD_PLAYER_CONTROL_HXX
Warren Dukes's avatar
Warren Dukes committed
22

23
#include "output/Client.hxx"
24
#include "AudioFormat.hxx"
25 26
#include "thread/Mutex.hxx"
#include "thread/Cond.hxx"
27
#include "thread/Thread.hxx"
28
#include "CrossFade.hxx"
29
#include "Chrono.hxx"
30 31
#include "ReplayGainConfig.hxx"
#include "ReplayGainMode.hxx"
32
#include "MusicChunkPtr.hxx"
33

34
#include <exception>
35
#include <memory>
36

37
#include <stdint.h>
Warren Dukes's avatar
Warren Dukes committed
38

39
struct Tag;
40
class PlayerListener;
41
class PlayerOutputs;
42
class DetachedSong;
43

44 45 46 47
enum class PlayerState : uint8_t {
	STOP,
	PAUSE,
	PLAY
48
};
Warren Dukes's avatar
Warren Dukes committed
49

50 51 52 53 54
enum class PlayerCommand : uint8_t {
	NONE,
	EXIT,
	STOP,
	PAUSE,
55 56 57 58

	/**
	 * Seek to a certain position in the specified song.  This
	 * command can also be used to change the current song or
59 60 61
	 * start playback.  It "finishes" immediately, but
	 * PlayerControl::seeking will be set until seeking really
	 * completes (or fails).
62
	 */
63
	SEEK,
64

65
	CLOSE_AUDIO,
66

67
	/**
68
	 * At least one AudioOutput.enabled flag has been modified;
69 70
	 * commit those changes to the output threads.
	 */
71
	UPDATE_AUDIO,
72

73
	/** PlayerControl.next_song has been updated */
74
	QUEUE,
75 76

	/**
77
	 * cancel pre-decoding PlayerControl.next_song; if the player
78 79 80
	 * has already started playing this song, it will completely
	 * stop
	 */
81
	CANCEL,
82 83

	/**
84
	 * Refresh status information in the #PlayerControl struct,
85 86
	 * e.g. elapsed_time.
	 */
87
	REFRESH,
88 89
};

90 91
enum class PlayerError : uint8_t {
	NONE,
92 93 94 95

	/**
	 * The decoder has failed to decode the song.
	 */
96
	DECODER,
97 98 99 100

	/**
	 * The audio output has failed.
	 */
101
	OUTPUT,
102
};
Warren Dukes's avatar
Warren Dukes committed
103

104
struct PlayerStatus {
105
	PlayerState state;
106
	uint16_t bit_rate;
107
	AudioFormat audio_format;
108
	SignedSongTime total_time;
109
	SongTime elapsed_time;
110 111
};

112 113 114
class PlayerControl final : public AudioOutputClient {
	friend class Player;

115 116
	PlayerListener &listener;

117
	PlayerOutputs &outputs;
118

119
	const unsigned buffer_chunks;
120

121
	const unsigned buffered_before_play;
122

123 124 125 126 127
	/**
	 * The "audio_output_format" setting.
	 */
	const AudioFormat configured_audio_format;

128 129 130 131
	/**
	 * The handle of the player thread.
	 */
	Thread thread;
132

133
	/**
134
	 * This lock protects #command, #state, #error, #tagged_song.
135
	 */
136
	mutable Mutex mutex;
137 138 139 140

	/**
	 * Trigger this object after you have modified #command.
	 */
141
	Cond cond;
142

143 144 145 146 147 148 149
	/**
	 * This object gets signalled when the player thread has
	 * finished the #command.  It wakes up the client that waits
	 * (i.e. the main thread).
	 */
	Cond client_cond;

150 151
	/**
	 * The error that occurred in the player thread.  This
152
	 * attribute is only valid if #error_type is not
153 154
	 * #PlayerError::NONE.  The object must be freed when this
	 * object transitions back to #PlayerError::NONE.
155
	 */
156
	std::exception_ptr error;
157

158 159 160 161 162 163 164 165
	/**
	 * The next queued song.
	 *
	 * This is a duplicate, and must be freed when this attribute
	 * is cleared.
	 */
	std::unique_ptr<DetachedSong> next_song;

166
	/**
167 168 169
	 * A copy of the current #DetachedSong after its tags have
	 * been updated by the decoder (for example, a radio stream
	 * that has sent a new tag after switching to the next song).
170 171
	 * This shall be used by PlayerListener::OnPlayerTagModified()
	 * to update the current #DetachedSong in the queue.
172 173 174 175
	 *
	 * Protected by #mutex.  Set by the PlayerThread and consumed
	 * by the main thread.
	 */
176
	std::unique_ptr<DetachedSong> tagged_song;
177

178 179
	PlayerCommand command = PlayerCommand::NONE;
	PlayerState state = PlayerState::STOP;
180

181
	PlayerError error_type = PlayerError::NONE;
182

183 184
	ReplayGainMode replay_gain_mode = ReplayGainMode::OFF;

185 186 187 188 189
	/**
	 * Is the player currently busy with the SEEK command?
	 */
	bool seeking = false;

190 191 192 193 194 195 196
	/**
	 * If this flag is set, then the player will be auto-paused at
	 * the end of the song, before the next song starts to play.
	 *
	 * This is a copy of the queue's "single" flag most of the
	 * time.
	 */
197
	bool border_pause = false;
Warren Dukes's avatar
Warren Dukes committed
198

199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221
	/**
	 * If this flag is set, then the player thread is currently
	 * occupied and will not be able to respond quickly to
	 * commands (e.g. waiting for the decoder thread to finish
	 * seeking).  This is used to skip #PlayerCommand::REFRESH to
	 * avoid blocking the main thread.
	 */
	bool occupied = false;

	struct ScopeOccupied {
		PlayerControl &pc;

		explicit ScopeOccupied(PlayerControl &_pc) noexcept:pc(_pc) {
			assert(!pc.occupied);
			pc.occupied = true;
		}

		~ScopeOccupied() noexcept {
			assert(pc.occupied);
			pc.occupied = false;
		}
	};

222 223 224 225 226 227 228 229 230 231 232 233 234 235
	AudioFormat audio_format;
	uint16_t bit_rate;

	SignedSongTime total_time;
	SongTime elapsed_time;

	SongTime seek_time;

	CrossFadeSettings cross_fade;

	const ReplayGainConfig replay_gain_config;

	double total_play_time = 0;

236
public:
237
	PlayerControl(PlayerListener &_listener,
238
		      PlayerOutputs &_outputs,
239
		      unsigned buffer_chunks,
240
		      unsigned buffered_before_play,
241
		      AudioFormat _configured_audio_format,
242 243
		      const ReplayGainConfig &_replay_gain_config) noexcept;
	~PlayerControl() noexcept;
244

245 246 247 248 249 250 251
	/**
	 * Throws on error.
	 */
	void StartThread() {
		thread.Start();
	}

252 253 254
	/**
	 * Locks the object.
	 */
255
	void Lock() const noexcept {
256 257
		mutex.lock();
	}
258

259 260 261
	/**
	 * Unlocks the object.
	 */
262
	void Unlock() const noexcept {
263 264
		mutex.unlock();
	}
Warren Dukes's avatar
Warren Dukes committed
265

266 267 268 269
	/**
	 * Signals the object.  The object should be locked prior to
	 * calling this function.
	 */
270
	void Signal() noexcept {
271 272
		cond.signal();
	}
273

274
private:
275 276 277 278
	/**
	 * Signals the object.  The object is temporarily locked by
	 * this function.
	 */
279
	void LockSignal() noexcept {
280
		const std::lock_guard<Mutex> protect(mutex);
281 282
		Signal();
	}
283

284 285 286 287 288
	/**
	 * Waits for a signal on the object.  This function is only
	 * valid in the player thread.  The object must be locked
	 * prior to calling this function.
	 */
289
	void Wait() noexcept {
290
		assert(thread.IsInside());
291

292 293
		cond.wait(mutex);
	}
Warren Dukes's avatar
Warren Dukes committed
294

295 296 297 298 299
	/**
	 * Wake up the client waiting for command completion.
	 *
	 * Caller must lock the object.
	 */
300
	void ClientSignal() noexcept {
301
		assert(thread.IsInside());
302 303 304 305 306 307 308 309 310 311

		client_cond.signal();
	}

	/**
	 * The client calls this method to wait for command
	 * completion.
	 *
	 * Caller must lock the object.
	 */
312
	void ClientWait() noexcept {
313
		assert(!thread.IsInside());
314 315 316 317

		client_cond.wait(mutex);
	}

318 319 320 321 322 323 324
	/**
	 * A command has been finished.  This method clears the
	 * command and signals the client.
	 *
	 * To be called from the player thread.  Caller must lock the
	 * object.
	 */
325
	void CommandFinished() noexcept {
326
		assert(command != PlayerCommand::NONE);
327

328
		command = PlayerCommand::NONE;
329 330 331
		ClientSignal();
	}

332
	void LockCommandFinished() noexcept {
333
		const std::lock_guard<Mutex> protect(mutex);
334 335 336
		CommandFinished();
	}

337 338 339 340 341 342 343 344 345 346
	/**
	 * Checks if the size of the #MusicPipe is below the #threshold.  If
	 * not, it attempts to synchronize with all output threads, and waits
	 * until another #MusicChunk is finished.
	 *
	 * Caller must lock the mutex.
	 *
	 * @param threshold the maximum number of chunks in the pipe
	 * @return true if there are less than #threshold chunks in the pipe
	 */
347
	bool WaitOutputConsumed(unsigned threshold) noexcept;
348

349
	bool LockWaitOutputConsumed(unsigned threshold) noexcept {
350
		const std::lock_guard<Mutex> protect(mutex);
351 352 353
		return WaitOutputConsumed(threshold);
	}

354 355 356 357 358 359
	/**
	 * Wait for the command to be finished by the player thread.
	 *
	 * To be called from the main thread.  Caller must lock the
	 * object.
	 */
360
	void WaitCommandLocked() noexcept {
361
		while (command != PlayerCommand::NONE)
362 363 364 365 366 367 368 369 370 371
			ClientWait();
	}

	/**
	 * Send a command to the player thread and synchronously wait
	 * for it to finish.
	 *
	 * To be called from the main thread.  Caller must lock the
	 * object.
	 */
372
	void SynchronousCommand(PlayerCommand cmd) noexcept {
373
		assert(command == PlayerCommand::NONE);
374 375 376 377 378 379 380 381 382 383 384 385 386

		command = cmd;
		Signal();
		WaitCommandLocked();
	}

	/**
	 * Send a command to the player thread and synchronously wait
	 * for it to finish.
	 *
	 * To be called from the main thread.  This method locks the
	 * object.
	 */
387
	void LockSynchronousCommand(PlayerCommand cmd) noexcept {
388
		const std::lock_guard<Mutex> protect(mutex);
389 390 391 392
		SynchronousCommand(cmd);
	}

public:
393
	/**
394
	 * Throws on error.
395
	 *
396
	 * @param song the song to be queued
397
	 */
398
	void Play(std::unique_ptr<DetachedSong> song);
399

400
	/**
401
	 * see PlayerCommand::CANCEL
402
	 */
403
	void LockCancel() noexcept;
Warren Dukes's avatar
Warren Dukes committed
404

405
	void LockSetPause(bool pause_flag) noexcept;
Warren Dukes's avatar
Warren Dukes committed
406

407
private:
408
	void PauseLocked() noexcept;
409

410
	void ClearError() noexcept {
411
		error_type = PlayerError::NONE;
412
		error = std::exception_ptr();
413 414
	}

415
public:
416
	void LockPause() noexcept;
Warren Dukes's avatar
Warren Dukes committed
417

418 419 420
	/**
	 * Set the player's #border_pause flag.
	 */
421
	void LockSetBorderPause(bool border_pause) noexcept;
422

423
private:
424
	bool ApplyBorderPause() noexcept {
425 426 427 428 429
		if (border_pause)
			state = PlayerState::PAUSE;
		return border_pause;
	}

430
public:
431
	void Kill() noexcept;
Warren Dukes's avatar
Warren Dukes committed
432

433
	gcc_pure
434
	PlayerStatus LockGetStatus() noexcept;
Warren Dukes's avatar
Warren Dukes committed
435

436
	PlayerState GetState() const noexcept {
437 438
		return state;
	}
Warren Dukes's avatar
Warren Dukes committed
439

440
private:
441 442 443 444 445
	/**
	 * Set the error.  Discards any previous error condition.
	 *
	 * Caller must lock the object.
	 *
446
	 * @param type the error type; must not be #PlayerError::NONE
447
	 */
448
	void SetError(PlayerError type, std::exception_ptr &&_error) noexcept;
449

450 451 452
	/**
	 * Set the error and set state to PlayerState::PAUSE.
	 */
453
	void SetOutputError(std::exception_ptr &&_error) noexcept {
454 455 456 457 458 459 460
		SetError(PlayerError::OUTPUT, std::move(_error));

		/* pause: the user may resume playback as soon as an
		   audio output becomes available */
		state = PlayerState::PAUSE;
	}

461
	void LockSetOutputError(std::exception_ptr &&_error) noexcept {
462
		const std::lock_guard<Mutex> lock(mutex);
463 464 465
		SetOutputError(std::move(_error));
	}

466
	/**
467 468
	 * Checks whether an error has occurred, and if so, rethrows
	 * it.
469 470 471
	 *
	 * Caller must lock the object.
	 */
472
	void CheckRethrowError() const {
473
		if (error_type != PlayerError::NONE)
474
			std::rethrow_exception(error);
475
	}
476

477
public:
478
	/**
479
	 * Like CheckRethrowError(), but locks and unlocks the object.
480
	 */
481
	void LockCheckRethrowError() const {
482
		const std::lock_guard<Mutex> protect(mutex);
483
		CheckRethrowError();
484 485
	}

486
	void LockClearError() noexcept;
Warren Dukes's avatar
Warren Dukes committed
487

488
	PlayerError GetErrorType() const noexcept {
489 490 491
		return error_type;
	}

492
private:
493 494
	/**
	 * Set the #tagged_song attribute to a newly allocated copy of
495
	 * the given #DetachedSong.  Locks and unlocks the object.
496
	 */
497
	void LockSetTaggedSong(const DetachedSong &song) noexcept;
498

499
	void ClearTaggedSong() noexcept;
500 501 502 503 504 505

	/**
	 * Read and clear the #tagged_song attribute.
	 *
	 * Caller must lock the object.
	 */
506
	std::unique_ptr<DetachedSong> ReadTaggedSong() noexcept;
507

508
public:
509 510 511
	/**
	 * Like ReadTaggedSong(), but locks and unlocks the object.
	 */
512
	std::unique_ptr<DetachedSong> LockReadTaggedSong() noexcept;
513

514
	void LockStop() noexcept;
515

516
	void LockUpdateAudio() noexcept;
517

518
private:
519
	void EnqueueSongLocked(std::unique_ptr<DetachedSong> song) noexcept;
520

521
	/**
522
	 * Throws on error.
523
	 */
524
	void SeekLocked(std::unique_ptr<DetachedSong> song, SongTime t);
525

526
public:
527 528 529 530
	/**
	 * @param song the song to be queued; the given instance will be owned
	 * and freed by the player
	 */
531
	void LockEnqueueSong(std::unique_ptr<DetachedSong> song) noexcept;
532

533 534 535 536
	bool HasNextSong() const noexcept {
		return next_song != nullptr;
	}

537 538 539
	/**
	 * Makes the player thread seek the specified song to a position.
	 *
540
	 * Throws on error.
541
	 *
542 543 544
	 * @param song the song to be queued; the given instance will be owned
	 * and freed by the player
	 */
545
	void LockSeek(std::unique_ptr<DetachedSong> song, SongTime t);
546

547 548 549 550 551 552 553 554 555 556 557 558 559
private:
	/**
	 * Caller must lock the object.
	 */
	void CancelPendingSeek() noexcept {
		if (!seeking)
			return;

		seeking = false;
		ClientSignal();
	}

public:
560
	void SetCrossFade(float cross_fade_seconds) noexcept;
561

562
	float GetCrossFade() const noexcept {
563
		return cross_fade.duration;
564 565
	}

566
	void SetMixRampDb(float mixramp_db) noexcept;
567

568
	float GetMixRampDb() const noexcept {
569
		return cross_fade.mixramp_db;
570 571
	}

572
	void SetMixRampDelay(float mixramp_delay_seconds) noexcept;
573

574
	float GetMixRampDelay() const noexcept {
575
		return cross_fade.mixramp_delay;
576 577
	}

578
	void LockSetReplayGainMode(ReplayGainMode _mode) noexcept {
579
		const std::lock_guard<Mutex> protect(mutex);
580 581 582
		replay_gain_mode = _mode;
	}

583
	double GetTotalPlayTime() const noexcept {
584 585
		return total_play_time;
	}
586

587
private:
588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603
	void LockUpdateSongTag(DetachedSong &song,
			       const Tag &new_tag) noexcept;

	/**
	 * Plays a #MusicChunk object (after applying software
	 * volume).  If it contains a (stream) tag, copy it to the
	 * current song, so MPD's playlist reflects the new stream
	 * tag.
	 *
	 * Player lock is not held.
	 *
	 * Throws on error.
	 */
	void PlayChunk(DetachedSong &song, MusicChunkPtr chunk,
		       const AudioFormat &format);

604 605 606 607 608 609 610 611
	/* virtual methods from AudioOutputClient */
	void ChunksConsumed() override {
		LockSignal();
	}

	void ApplyEnabled() override {
		LockUpdateAudio();
	}
612 613

private:
614
	void RunThread() noexcept;
615
};
616

Warren Dukes's avatar
Warren Dukes committed
617
#endif