mpd/src/player/Control.hxx

471 lines
9.2 KiB
C++
Raw Normal View History

/*
2015-01-01 19:48:13 +01:00
* Copyright (C) 2003-2015 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
2013-08-03 21:00:50 +02:00
#include "AudioFormat.hxx"
#include "thread/Mutex.hxx"
#include "thread/Cond.hxx"
#include "thread/Thread.hxx"
#include "util/Error.hxx"
#include "CrossFade.hxx"
#include "Chrono.hxx"
#include <stdint.h>
class PlayerListener;
class MultipleOutputs;
class DetachedSong;
enum class PlayerState : uint8_t {
STOP,
PAUSE,
PLAY
};
enum class PlayerCommand : uint8_t {
NONE,
EXIT,
STOP,
PAUSE,
SEEK,
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 player_status {
PlayerState state;
uint16_t bit_rate;
2013-08-03 21:00:50 +02:00
AudioFormat audio_format;
SignedSongTime total_time;
SongTime elapsed_time;
};
struct PlayerControl {
PlayerListener &listener;
MultipleOutputs &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 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;
PlayerCommand command;
PlayerState state;
PlayerError error_type;
/**
* The error that occurred in the player thread. This
* attribute is only valid if #error is not
* #PlayerError::NONE. The object must be freed when this
* object transitions back to #PlayerError::NONE.
*/
Error error;
/**
* 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.
*/
DetachedSong *tagged_song;
uint16_t bit_rate;
2013-08-03 21:00:50 +02:00
AudioFormat audio_format;
SignedSongTime total_time;
SongTime elapsed_time;
/**
* The next queued song.
*
* This is a duplicate, and must be freed when this attribute
* is cleared.
*/
DetachedSong *next_song;
SongTime seek_time;
CrossFadeSettings cross_fade;
double total_play_time;
/**
* 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.
*/
bool border_pause;
PlayerControl(PlayerListener &_listener,
MultipleOutputs &_outputs,
unsigned buffer_chunks,
unsigned buffered_before_play);
~PlayerControl();
/**
* Locks the object.
*/
void Lock() const {
mutex.lock();
}
/**
* Unlocks the object.
*/
void Unlock() const {
mutex.unlock();
}
/**
* Signals the object. The object should be locked prior to
* calling this function.
*/
void Signal() {
cond.signal();
}
/**
* Signals the object. The object is temporarily locked by
* this function.
*/
void LockSignal() {
Lock();
Signal();
Unlock();
}
/**
* 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.
*/
void Wait() {
assert(thread.IsInside());
cond.wait(mutex);
}
/**
* Wake up the client waiting for command completion.
*
* Caller must lock the object.
*/
void ClientSignal() {
assert(thread.IsInside());
client_cond.signal();
}
/**
* The client calls this method to wait for command
* completion.
*
* Caller must lock the object.
*/
void ClientWait() {
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.
*/
void CommandFinished() {
assert(command != PlayerCommand::NONE);
command = PlayerCommand::NONE;
ClientSignal();
}
void LockCommandFinished() {
Lock();
CommandFinished();
Unlock();
}
private:
/**
* Wait for the command to be finished by the player thread.
*
* To be called from the main thread. Caller must lock the
* object.
*/
void WaitCommandLocked() {
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) {
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) {
Lock();
SynchronousCommand(cmd);
Unlock();
}
public:
/**
* @param song the song to be queued; the given instance will
* be owned and freed by the player
*/
void Play(DetachedSong *song);
/**
* see PlayerCommand::CANCEL
*/
void Cancel();
void SetPause(bool pause_flag);
private:
void PauseLocked();
public:
void Pause();
/**
* Set the player's #border_pause flag.
*/
void SetBorderPause(bool border_pause);
void Kill();
gcc_pure
player_status GetStatus();
PlayerState GetState() const {
return state;
}
/**
* Set the error. Discards any previous error condition.
*
* Caller must lock the object.
*
* @param type the error type; must not be #PlayerError::NONE
* @param error detailed error information; must be defined.
*/
void SetError(PlayerError type, Error &&error);
/**
* Checks whether an error has occurred, and if so, returns a
* copy of the #Error object.
*
* Caller must lock the object.
*/
gcc_pure
Error GetError() const {
Error result;
if (error_type != PlayerError::NONE)
result.Set(error);
return result;
}
/**
* Like GetError(), but locks and unlocks the object.
*/
gcc_pure
Error LockGetError() const {
Lock();
Error result = GetError();
Unlock();
return result;
}
void ClearError();
PlayerError GetErrorType() const {
return error_type;
}
/**
* Set the #tagged_song attribute to a newly allocated copy of
* the given #DetachedSong. Locks and unlocks the object.
*/
void LockSetTaggedSong(const DetachedSong &song);
void ClearTaggedSong();
/**
* Read and clear the #tagged_song attribute.
*
* Caller must lock the object.
*/
DetachedSong *ReadTaggedSong() {
DetachedSong *result = tagged_song;
tagged_song = nullptr;
return result;
}
/**
* Like ReadTaggedSong(), but locks and unlocks the object.
*/
DetachedSong *LockReadTaggedSong() {
Lock();
DetachedSong *result = ReadTaggedSong();
Unlock();
return result;
}
void Stop();
void UpdateAudio();
private:
void EnqueueSongLocked(DetachedSong *song) {
assert(song != nullptr);
assert(next_song == nullptr);
next_song = song;
seek_time = SongTime::zero();
SynchronousCommand(PlayerCommand::QUEUE);
}
public:
/**
* @param song the song to be queued; the given instance will be owned
* and freed by the player
*/
void EnqueueSong(DetachedSong *song);
/**
* Makes the player thread seek the specified song to a position.
*
* @param song the song to be queued; the given instance will be owned
* and freed by the player
* @return true on success, false on failure (e.g. if MPD isn't
* playing currently)
*/
bool Seek(DetachedSong *song, SongTime t);
void SetCrossFade(float cross_fade_seconds);
float GetCrossFade() const {
return cross_fade.duration;
}
void SetMixRampDb(float mixramp_db);
float GetMixRampDb() const {
return cross_fade.mixramp_db;
}
void SetMixRampDelay(float mixramp_delay_seconds);
float GetMixRampDelay() const {
return cross_fade.mixramp_delay;
}
double GetTotalPlayTime() const {
return total_play_time;
}
};
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