mpd/src/player/Control.hxx

618 lines
13 KiB
C++
Raw Normal View History

/*
2017-01-03 20:48:59 +01:00
* Copyright 2003-2017 The Music Player Daemon Project
* http://www.musicpd.org
*
* 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.
*
* 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.
*/
2014-08-30 00:02:11 +02:00
#ifndef MPD_PLAYER_CONTROL_HXX
#define MPD_PLAYER_CONTROL_HXX
#include "output/Client.hxx"
2013-08-03 21:00:50 +02:00
#include "AudioFormat.hxx"
#include "thread/Mutex.hxx"
#include "thread/Cond.hxx"
#include "thread/Thread.hxx"
#include "CrossFade.hxx"
#include "Chrono.hxx"
#include "ReplayGainConfig.hxx"
#include "ReplayGainMode.hxx"
#include "MusicChunkPtr.hxx"
#include <exception>
#include <memory>
#include <stdint.h>
struct Tag;
class PlayerListener;
class PlayerOutputs;
class DetachedSong;
enum class PlayerState : uint8_t {
STOP,
PAUSE,
PLAY
};
enum class PlayerCommand : uint8_t {
NONE,
EXIT,
STOP,
PAUSE,
2017-12-20 11:24:35 +01:00
/**
* Seek to a certain position in the specified song. This
* command can also be used to change the current song or
* start playback. It "finishes" immediately, but
* PlayerControl::seeking will be set until seeking really
* completes (or fails).
2017-12-20 11:24:35 +01:00
*/
SEEK,
2017-12-20 11:24:35 +01:00
CLOSE_AUDIO,
/**
* At least one AudioOutput.enabled flag has been modified;
* commit those changes to the output threads.
*/
UPDATE_AUDIO,
/** PlayerControl.next_song has been updated */
QUEUE,
/**
* cancel pre-decoding PlayerControl.next_song; if the player
* has already started playing this song, it will completely
* stop
*/
CANCEL,
/**
* Refresh status information in the #PlayerControl struct,
* e.g. elapsed_time.
*/
REFRESH,
};
enum class PlayerError : uint8_t {
NONE,
/**
* The decoder has failed to decode the song.
*/
DECODER,
/**
* The audio output has failed.
*/
OUTPUT,
};
struct PlayerStatus {
PlayerState state;
uint16_t bit_rate;
2013-08-03 21:00:50 +02:00
AudioFormat audio_format;
SignedSongTime total_time;
SongTime elapsed_time;
};
2018-09-21 18:00:23 +02:00
class PlayerControl final : public AudioOutputClient {
friend class Player;
PlayerListener &listener;
PlayerOutputs &outputs;
2014-08-30 00:03:14 +02:00
const unsigned buffer_chunks;
2014-08-30 00:03:14 +02:00
const unsigned buffered_before_play;
2008-08-26 08:45:14 +02:00
/**
* The "audio_output_format" setting.
*/
const AudioFormat configured_audio_format;
/**
* The handle of the player thread.
*/
Thread thread;
/**
* This lock protects #command, #state, #error, #tagged_song.
*/
mutable Mutex mutex;
/**
* Trigger this object after you have modified #command.
*/
Cond cond;
/**
* 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;
/**
* The error that occurred in the player thread. This
* attribute is only valid if #error_type is not
* #PlayerError::NONE. The object must be freed when this
* object transitions back to #PlayerError::NONE.
*/
std::exception_ptr error;
/**
* The next queued song.
*
* This is a duplicate, and must be freed when this attribute
* is cleared.
*/
std::unique_ptr<DetachedSong> next_song;
/**
* 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).
* This shall be used by PlayerListener::OnPlayerTagModified()
* to update the current #DetachedSong in the queue.
*
* Protected by #mutex. Set by the PlayerThread and consumed
* by the main thread.
*/
std::unique_ptr<DetachedSong> tagged_song;
PlayerCommand command = PlayerCommand::NONE;
PlayerState state = PlayerState::STOP;
PlayerError error_type = PlayerError::NONE;
ReplayGainMode replay_gain_mode = ReplayGainMode::OFF;
/**
* Is the player currently busy with the SEEK command?
*/
bool seeking = false;
/**
* 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.
*/
2016-12-03 13:08:00 +01:00
bool border_pause = false;
/**
* 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;
}
};
AudioFormat audio_format;
uint16_t bit_rate;
SignedSongTime total_time;
SongTime elapsed_time;
SongTime seek_time;
CrossFadeSettings cross_fade;
const ReplayGainConfig replay_gain_config;
FloatDuration total_play_time = FloatDuration::zero();
2018-09-21 18:00:23 +02:00
public:
PlayerControl(PlayerListener &_listener,
PlayerOutputs &_outputs,
unsigned buffer_chunks,
unsigned buffered_before_play,
AudioFormat _configured_audio_format,
2017-11-26 12:02:08 +01:00
const ReplayGainConfig &_replay_gain_config) noexcept;
~PlayerControl() noexcept;
/**
* Throws on error.
*/
void StartThread() {
thread.Start();
}
/**
* Locks the object.
*/
2017-11-26 12:02:08 +01:00
void Lock() const noexcept {
mutex.lock();
}
/**
* Unlocks the object.
*/
2017-11-26 12:02:08 +01:00
void Unlock() const noexcept {
mutex.unlock();
}
/**
* Signals the object. The object should be locked prior to
* calling this function.
*/
2017-11-26 12:02:08 +01:00
void Signal() noexcept {
cond.signal();
}
2018-09-21 18:00:23 +02:00
private:
/**
* Signals the object. The object is temporarily locked by
* this function.
*/
2017-11-26 12:02:08 +01:00
void LockSignal() noexcept {
const std::lock_guard<Mutex> protect(mutex);
Signal();
}
/**
* 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.
*/
2017-11-26 12:02:08 +01:00
void Wait() noexcept {
assert(thread.IsInside());
cond.wait(mutex);
}
/**
* Wake up the client waiting for command completion.
*
* Caller must lock the object.
*/
2017-11-26 12:02:08 +01:00
void ClientSignal() noexcept {
assert(thread.IsInside());
client_cond.signal();
}
/**
* The client calls this method to wait for command
* completion.
*
* Caller must lock the object.
*/
2017-11-26 12:02:08 +01:00
void ClientWait() noexcept {
assert(!thread.IsInside());
client_cond.wait(mutex);
}
/**
* 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.
*/
2017-11-26 12:02:08 +01:00
void CommandFinished() noexcept {
assert(command != PlayerCommand::NONE);
command = PlayerCommand::NONE;
ClientSignal();
}
2017-11-26 12:02:08 +01:00
void LockCommandFinished() noexcept {
const std::lock_guard<Mutex> protect(mutex);
CommandFinished();
}
/**
* 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
*/
2017-11-26 12:02:08 +01:00
bool WaitOutputConsumed(unsigned threshold) noexcept;
2017-11-26 12:02:08 +01:00
bool LockWaitOutputConsumed(unsigned threshold) noexcept {
const std::lock_guard<Mutex> protect(mutex);
return WaitOutputConsumed(threshold);
}
/**
* Wait for the command to be finished by the player thread.
*
* To be called from the main thread. Caller must lock the
* object.
*/
2017-11-26 12:02:08 +01:00
void WaitCommandLocked() noexcept {
while (command != PlayerCommand::NONE)
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.
*/
void SynchronousCommand(PlayerCommand cmd) noexcept {
assert(command == PlayerCommand::NONE);
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.
*/
void LockSynchronousCommand(PlayerCommand cmd) noexcept {
const std::lock_guard<Mutex> protect(mutex);
SynchronousCommand(cmd);
}
public:
/**
* Throws on error.
*
* @param song the song to be queued
*/
void Play(std::unique_ptr<DetachedSong> song);
/**
* see PlayerCommand::CANCEL
*/
2017-11-26 12:02:08 +01:00
void LockCancel() noexcept;
2017-11-26 12:02:08 +01:00
void LockSetPause(bool pause_flag) noexcept;
private:
2017-11-26 12:02:08 +01:00
void PauseLocked() noexcept;
2017-11-26 12:02:08 +01:00
void ClearError() noexcept {
error_type = PlayerError::NONE;
error = std::exception_ptr();
}
public:
2017-11-26 12:02:08 +01:00
void LockPause() noexcept;
/**
* Set the player's #border_pause flag.
*/
2017-11-26 12:02:08 +01:00
void LockSetBorderPause(bool border_pause) noexcept;
2018-09-21 18:00:23 +02:00
private:
2017-11-26 12:02:08 +01:00
bool ApplyBorderPause() noexcept {
if (border_pause)
state = PlayerState::PAUSE;
return border_pause;
}
2018-09-21 18:00:23 +02:00
public:
2017-11-26 12:02:08 +01:00
void Kill() noexcept;
gcc_pure
PlayerStatus LockGetStatus() noexcept;
2017-11-26 12:02:08 +01:00
PlayerState GetState() const noexcept {
return state;
}
2018-09-21 18:00:23 +02:00
private:
/**
* Set the error. Discards any previous error condition.
*
* Caller must lock the object.
*
* @param type the error type; must not be #PlayerError::NONE
*/
2017-11-26 12:02:08 +01:00
void SetError(PlayerError type, std::exception_ptr &&_error) noexcept;
/**
* Set the error and set state to PlayerState::PAUSE.
*/
2017-11-26 12:02:08 +01:00
void SetOutputError(std::exception_ptr &&_error) noexcept {
SetError(PlayerError::OUTPUT, std::move(_error));
/* pause: the user may resume playback as soon as an
audio output becomes available */
state = PlayerState::PAUSE;
}
2017-11-26 12:02:08 +01:00
void LockSetOutputError(std::exception_ptr &&_error) noexcept {
const std::lock_guard<Mutex> lock(mutex);
SetOutputError(std::move(_error));
}
/**
* Checks whether an error has occurred, and if so, rethrows
* it.
*
* Caller must lock the object.
*/
void CheckRethrowError() const {
if (error_type != PlayerError::NONE)
std::rethrow_exception(error);
}
2018-09-21 18:00:23 +02:00
public:
/**
* Like CheckRethrowError(), but locks and unlocks the object.
*/
void LockCheckRethrowError() const {
const std::lock_guard<Mutex> protect(mutex);
CheckRethrowError();
}
2017-11-26 12:02:08 +01:00
void LockClearError() noexcept;
2017-11-26 12:02:08 +01:00
PlayerError GetErrorType() const noexcept {
return error_type;
}
2018-09-21 18:00:23 +02:00
private:
/**
* Set the #tagged_song attribute to a newly allocated copy of
* the given #DetachedSong. Locks and unlocks the object.
*/
2017-11-26 12:02:08 +01:00
void LockSetTaggedSong(const DetachedSong &song) noexcept;
2017-11-26 12:02:08 +01:00
void ClearTaggedSong() noexcept;
/**
* Read and clear the #tagged_song attribute.
*
* Caller must lock the object.
*/
std::unique_ptr<DetachedSong> ReadTaggedSong() noexcept;
2018-09-21 18:00:23 +02:00
public:
/**
* Like ReadTaggedSong(), but locks and unlocks the object.
*/
std::unique_ptr<DetachedSong> LockReadTaggedSong() noexcept;
2017-11-26 12:02:08 +01:00
void LockStop() noexcept;
2017-11-26 12:02:08 +01:00
void LockUpdateAudio() noexcept;
private:
void EnqueueSongLocked(std::unique_ptr<DetachedSong> song) noexcept;
/**
* Throws on error.
*/
void SeekLocked(std::unique_ptr<DetachedSong> song, SongTime t);
public:
/**
* @param song the song to be queued; the given instance will be owned
* and freed by the player
*/
void LockEnqueueSong(std::unique_ptr<DetachedSong> song) noexcept;
2018-09-21 18:00:23 +02:00
bool HasNextSong() const noexcept {
return next_song != nullptr;
}
/**
* Makes the player thread seek the specified song to a position.
*
* Throws on error.
*
* @param song the song to be queued; the given instance will be owned
* and freed by the player
*/
void LockSeek(std::unique_ptr<DetachedSong> song, SongTime t);
2018-09-21 18:00:23 +02:00
private:
/**
* Caller must lock the object.
*/
void CancelPendingSeek() noexcept {
if (!seeking)
return;
seeking = false;
ClientSignal();
}
public:
2017-11-26 12:02:08 +01:00
void SetCrossFade(float cross_fade_seconds) noexcept;
2017-11-26 12:02:08 +01:00
float GetCrossFade() const noexcept {
return cross_fade.duration;
}
2017-11-26 12:02:08 +01:00
void SetMixRampDb(float mixramp_db) noexcept;
2017-11-26 12:02:08 +01:00
float GetMixRampDb() const noexcept {
return cross_fade.mixramp_db;
}
2017-11-26 12:02:08 +01:00
void SetMixRampDelay(float mixramp_delay_seconds) noexcept;
2017-11-26 12:02:08 +01:00
float GetMixRampDelay() const noexcept {
return cross_fade.mixramp_delay;
}
2017-11-26 12:02:08 +01:00
void LockSetReplayGainMode(ReplayGainMode _mode) noexcept {
const std::lock_guard<Mutex> protect(mutex);
replay_gain_mode = _mode;
}
auto GetTotalPlayTime() const noexcept {
return total_play_time;
}
2018-09-21 18:00:23 +02:00
private:
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);
/* virtual methods from AudioOutputClient */
void ChunksConsumed() override {
LockSignal();
}
void ApplyEnabled() override {
LockUpdateAudio();
}
2017-02-10 22:41:11 +01:00
private:
2017-11-26 12:02:08 +01:00
void RunThread() noexcept;
};
Initial cut of fork() => pthreads() for decoder and player I initially started to do a heavy rewrite that changed the way processes communicated, but that was too much to do at once. So this change only focuses on replacing the player and decode processes with threads and using condition variables instead of polling in loops; so the changeset itself is quiet small. * The shared output buffer variables will still need locking to guard against race conditions. So in this effect, we're probably just as buggy as before. The reduced context-switching overhead of using threads instead of processes may even make bugs show up more or less often... * Basic functionality appears to be working for playing local (and NFS) audio, including: play, pause, stop, seek, previous, next, and main playlist editing * I haven't tested HTTP streams yet, they should work. * I've only tested ALSA and Icecast. ALSA works fine, Icecast metadata seems to get screwy at times and breaks song advancement in the playlist at times. * state file loading works, too (after some last-minute hacks with non-blocking wakeup functions) * The non-blocking (*_nb) variants of the task management functions are probably overused. They're more lenient and easier to use because much of our code is still based on our previous polling-based system. * It currently segfaults on exit. I haven't paid much attention to the exit/signal-handling routines other than ensuring it compiles. At least the state file seems to work. We don't do any cleanups of the threads on exit, yet. * Update is still done in a child process and not in a thread. To do this in a thread, we'll need to ensure it does proper locking and communication with the main thread; but should require less memory in the end because we'll be updating the database "in-place" rather than updating a copy and then bulk-loading when done. * We're more sensitive to bugs in 3rd party libraries now. My plan is to eventually use a master process which forks() and restarts the child when it dies: locking and communication with the main thread; but should require less memory in the end because we'll be updating the database "in-place" rather than updating a copy and then bulk-loading when done. * We're more sensitive to bugs in 3rd party libraries now. My plan is to eventually use a master process which forks() and restarts the child when it dies: master - just does waitpid() + fork() in a loop \- main thread \- decoder thread \- player thread At the beginning of every song, the main thread will set a dirty flag and update the state file. This way, if we encounter a song that triggers a segfault killing the main thread, the master will start the replacement main on the next song. * The main thread still wakes up every second on select() to check for signals; which affects power management. [merged r7138 from branches/ew] git-svn-id: https://svn.musicpd.org/mpd/trunk@7240 09075e82-0dd4-0310-85a5-a0d7c8717e4f
2008-04-12 06:08:00 +02:00
#endif