2023-04-24 13:45:06 -07:00
# pragma once
2024-03-23 18:41:26 -07:00
# include <SDL_mixer.h>
2024-04-10 18:00:19 -07:00
extern " C " {
# include <vgmstream.h>
}
2024-04-28 12:31:40 -07:00
# ifdef __ANDROID__
# include <oboe/Oboe.h>
# endif
2023-04-24 13:45:06 -07:00
# include <thread>
2023-07-15 14:52:49 -07:00
# include <SDL.h>
# include <SDL_audio.h>
2023-04-24 13:45:06 -07:00
# include <string>
# include <atomic>
# include <mutex>
2023-07-15 14:52:49 -07:00
# include <SoundTouch.h>
# include <span>
2023-09-03 11:54:07 -07:00
# include <optional>
2023-12-22 14:13:48 -08:00
# include <vector>
# include <queue>
2024-04-09 10:15:05 -07:00
# include <deque>
# include <map>
2024-08-08 13:12:37 -07:00
# include "file_backend.hpp"
# include "playback_backend.hpp"
# include "playback_process.hpp"
2023-07-15 14:52:49 -07:00
using namespace soundtouch ;
using std : : span ;
2023-09-03 11:54:07 -07:00
using std : : optional ;
2023-12-22 14:13:48 -08:00
using std : : vector ;
using std : : queue ;
2024-04-09 10:15:05 -07:00
using std : : deque ;
2024-03-23 18:41:26 -07:00
enum {
2024-04-09 10:15:05 -07:00
/// @brief No signals have occurred.
2024-03-23 18:41:26 -07:00
PlaybackSignalNone = 0 ,
2024-04-09 10:15:05 -07:00
/// @brief The file was changed. Recheck the properties of the file because they are likely different.
2024-03-23 18:41:26 -07:00
PlaybackSignalFileChanged = 1 < < 0 ,
2024-04-09 10:15:05 -07:00
/// @brief The speed was changed.
2024-03-23 18:41:26 -07:00
PlaybackSignalSpeedChanged = 1 < < 1 ,
2024-04-09 10:15:05 -07:00
/// @brief The speed was changed.
2024-03-23 18:41:26 -07:00
PlaybackSignalTempoChanged = 1 < < 2 ,
2024-04-09 10:15:05 -07:00
/// @brief The speed was changed.
2024-03-23 18:41:26 -07:00
PlaybackSignalPitchChanged = 1 < < 3 ,
2024-04-09 10:15:05 -07:00
/// @brief Playback was paused. If @ref PlaybackSignalResumed has also been sent, you must use @ref Playback::IsPaused to check if playback was paused or resumed.
2024-03-23 18:41:26 -07:00
PlaybackSignalPaused = 1 < < 4 ,
2024-04-09 10:15:05 -07:00
/// @brief Playback was resumed. If @ref PlaybackSignalPaused has also been sent, you must use @ref Playback::IsPaused to check if playback was paused or resumed.
2024-03-23 18:41:26 -07:00
PlaybackSignalResumed = 1 < < 5 ,
2024-04-09 10:15:05 -07:00
/// @brief Playback was stopped entirely. If @ref PlaybackSignalStarted has also been signalled, call @ref Playback::IsStopped to find out if playback is currently playing.
2024-03-23 18:41:26 -07:00
PlaybackSignalStopped = 1 < < 6 ,
2024-04-09 10:15:05 -07:00
/// @brief An error occurred and playback has likely (but not necessarily) stopped. Call @ref Playback::GetError for details.
2024-03-23 18:41:26 -07:00
PlaybackSignalErrorOccurred = 1 < < 7 ,
2024-04-09 10:15:05 -07:00
/// @brief Playback was seeked by the @ref Playback::Seek function
2024-03-23 18:41:26 -07:00
PlaybackSignalSeeked = 1 < < 8 ,
2024-04-09 10:15:05 -07:00
/// @brief Playback has started. If @ref PlaybackSignalStopped has also been signalled, call @ref Playback::IsStopped to find out if playback is currently playing.
2024-03-23 18:41:26 -07:00
PlaybackSignalStarted = 1 < < 9
} ;
2024-04-09 10:15:05 -07:00
/// @brief Playback handler base class.
2023-04-24 13:45:06 -07:00
class Playback {
2024-04-09 10:15:05 -07:00
protected :
friend class DBusAPI ;
/// @brief The handle-specific signal state.
std : : map < void * , uint16_t > signals_occurred ;
/// @brief The global error state for initializing handle-specific state.
std : : vector < std : : string > errors ;
/// @brief The handle-specific error state.
std : : map < void * , std : : deque < std : : string > > errors_occurred ;
/// @brief The mutex for signals
std : : mutex signal_mutex ;
/// @brief The mutex for errors
std : : mutex error_mutex ;
/// @brief Signals other code with the specified signal(s). Use with PlaybackSignal* enum variants.
void set_signal ( uint16_t signal ) ;
/// @brief Signals an error, and pushes details to other code.
void set_error ( std : : string desc ) ;
2024-03-26 18:39:02 -07:00
public :
2024-04-09 10:15:05 -07:00
inline virtual bool is_proxy ( ) {
return false ;
}
2024-03-26 18:39:02 -07:00
inline Playback ( ) { } ;
inline virtual ~ Playback ( ) { }
2024-04-09 10:15:05 -07:00
/// @brief Gets the current file, if any.
/// @returns No value if there is no file playing, or the path to the file if there is a file playing.
2024-03-26 18:39:02 -07:00
inline virtual std : : optional < std : : string > get_current_file ( ) {
return { } ;
}
2024-04-09 10:15:05 -07:00
/// @brief Gets the current file's title, if any, for display to the user.
/// @returns No value if there is no file playing. If there is a file playing, a string is returned. The contents of the string depend on whether or not the file has a title tag. If so, the string contains the contents of that tag. Otherwise, the string contains the stem of the file's path.
2024-03-26 18:39:02 -07:00
inline virtual std : : optional < std : : string > get_current_title ( ) {
return { } ;
}
2024-04-13 12:56:39 -07:00
inline virtual int get_current_stream ( ) {
return 0 ;
}
2024-04-14 13:25:49 -07:00
inline virtual std : : vector < PlaybackStream > get_streams ( ) {
std : : vector < PlaybackStream > output ;
2024-04-13 12:56:39 -07:00
return output ;
}
inline virtual void play_stream ( int idx ) { }
2024-04-09 10:15:05 -07:00
/// @brief Gets the position of the playing file.
2024-03-26 18:39:02 -07:00
inline virtual double GetPosition ( ) {
return 0.0 ;
}
2024-04-09 10:15:05 -07:00
/// @brief Gets the length of the file. If the length is less than or equal to zero, the file can be considered to have an unknown length.
2024-03-26 18:39:02 -07:00
inline virtual double GetLength ( ) {
return 0.0 ;
}
2024-04-09 10:15:05 -07:00
/// @brief Sets the playback position into the current file.
2024-03-26 18:39:02 -07:00
inline virtual void Seek ( double position ) { }
2024-04-13 12:56:39 -07:00
/// @brief Loads a new file.
inline virtual void Load ( std : : string filePath ) { }
2024-04-09 10:15:05 -07:00
/// @brief Plays a new file.
2024-04-13 12:56:39 -07:00
inline virtual void Start ( std : : string filePath , int streamIdx = 0 ) { }
2024-04-09 10:15:05 -07:00
/// @brief Checks whether or not the file is paused.
/// @returns true if playback is paused, false otherwise
2024-03-26 18:39:02 -07:00
inline virtual bool IsPaused ( ) {
return true ;
}
2024-04-09 10:15:05 -07:00
/// @brief Toggles the pause state of the playback.
2024-03-26 18:39:02 -07:00
inline virtual void Pause ( ) { }
2024-04-09 10:15:05 -07:00
/// @brief Stops all playback.
2024-03-26 18:39:02 -07:00
inline virtual void Stop ( ) { }
2024-04-09 10:15:05 -07:00
/// @brief Checks whether or not playback is stopped.
/// @returns true if playback is stopped, false otherwise.
2024-03-26 18:39:02 -07:00
inline virtual bool IsStopped ( ) {
return true ;
}
2024-04-09 10:15:05 -07:00
/// @brief Sets the tempo multiplier of the playback.
2024-03-26 18:39:02 -07:00
inline virtual void SetTempo ( float tempo ) { }
2024-04-09 10:15:05 -07:00
/// @brief Sets the speed multiplier (Both speed and pitch, but this is a separate property) of the playback.
2024-03-26 18:39:02 -07:00
inline virtual void SetSpeed ( float speed ) { }
2024-04-09 10:15:05 -07:00
/// @brief Sets the pitch multiplier of the playback
2024-03-26 18:39:02 -07:00
inline virtual void SetPitch ( float pitch ) { }
2024-04-09 10:15:05 -07:00
/// @brief Sets the volume of the playback, as a percentage
2024-03-26 18:39:02 -07:00
inline virtual void SetVolume ( float volume ) { }
2024-04-09 10:15:05 -07:00
/// @brief Gets the tempo multiplier of the playback.
2024-03-26 18:39:02 -07:00
inline virtual float GetTempo ( ) {
return 1.0 ;
}
2024-04-09 10:15:05 -07:00
/// @brief Gets the speed multiplier (Both speed and pitch, but this is a separate property) of the playback.
2024-03-26 18:39:02 -07:00
inline virtual float GetSpeed ( ) {
return 1.0 ;
}
2024-04-09 10:15:05 -07:00
/// @brief Gets the pitch multiplier of the playback.
2024-03-26 18:39:02 -07:00
inline virtual float GetPitch ( ) {
return 1.0 ;
}
2024-04-09 10:15:05 -07:00
/// @brief Gets the volume of the playback, as a percentage
2024-03-26 18:39:02 -07:00
inline virtual float GetVolume ( ) {
return 1.0 ;
}
2024-04-09 10:15:05 -07:00
/// @brief Returns and clears signals, using a registered handle if specified.
/// @param signal The signal(s) to check for and clear.
/// @param handle The handle, which was specified to @ref Playback::register_handle , or nullptr.
uint16_t handle_signals ( uint16_t signal , void * handle = nullptr ) ;
/// @brief Registers a handle for usage in handle_signals and GetError.
void register_handle ( void * handle ) ;
/// @brief Unregisters a handle that is no longer needed.
void unregister_handle ( void * handle ) ;
/// @brief Gets the last error from the playback engine, or no value if there was none.
/// @param handle The handle as registered with @ref Playback::register_handle
inline virtual optional < std : : string > GetError ( void * handle ) {
std : : optional < std : : string > output = { } ;
if ( ErrorExists ( handle ) ) {
error_mutex . lock ( ) ;
if ( errors_occurred . contains ( handle ) ) {
output = errors_occurred [ handle ] . back ( ) ;
errors_occurred [ handle ] . pop_back ( ) ;
}
error_mutex . unlock ( ) ;
}
return output ;
2024-03-26 18:39:02 -07:00
}
2024-04-09 10:15:05 -07:00
/// @brief Checks if an unread error has occurred.
/// @param handle The handle as registered with @ref Playback::register_handle
inline virtual bool ErrorExists ( void * handle ) {
bool output = false ;
error_mutex . lock ( ) ;
if ( errors_occurred . contains ( handle ) ) {
output = ! errors_occurred [ handle ] . empty ( ) ;
}
error_mutex . unlock ( ) ;
return output ;
2024-03-26 18:39:02 -07:00
}
2024-04-09 10:15:05 -07:00
/// @brief Helper function to set the paused status via the @ref Playback::IsPaused and @ref Playback::Pause APIs.
/// @param paused The new pause state.
2024-03-26 18:39:02 -07:00
inline virtual void SetPaused ( bool paused ) {
if ( IsPaused ( ) ! = paused ) {
Pause ( ) ;
}
}
2024-04-24 09:59:51 -07:00
virtual void InitLoopFunction ( ) {
}
virtual void LoopFunction ( ) {
}
virtual void DeinitLoopFunction ( ) {
}
bool loop_started = false ;
virtual void start_loop ( ) {
InitLoopFunction ( ) ;
loop_started = true ;
}
virtual void stop_loop ( ) {
DeinitLoopFunction ( ) ;
loop_started = false ;
}
virtual void LoopHook ( ) {
if ( loop_started ) {
LoopFunction ( ) ;
}
}
2024-08-08 13:12:37 -07:00
2024-04-09 10:15:05 -07:00
static Playback * Create ( bool * daemon_found , bool daemon = false ) ;
2024-03-26 18:39:02 -07:00
} ;
class DBusAPISender ;
2024-04-28 12:31:40 -07:00
class PlaybackInstance : public Playback
# ifdef __ANDROID__
, public oboe : : AudioStreamDataCallback
# endif
{
2023-04-24 13:45:06 -07:00
private :
2024-04-28 12:31:40 -07:00
# ifdef __ANDROID__
std : : shared_ptr < oboe : : AudioStream > ostream ;
public :
oboe : : DataCallbackResult onAudioReady (
oboe : : AudioStream * audioStream ,
void * audioData ,
int32_t numFrames ) override ;
private :
# endif
2024-08-08 13:12:37 -07:00
PlaybackProcess * process ;
2023-04-24 13:45:06 -07:00
std : : string filePath ;
std : : atomic_bool running ;
std : : atomic_bool file_changed ;
std : : atomic_bool seeking ;
std : : atomic_bool update ;
2023-09-03 11:54:07 -07:00
std : : atomic_bool restart ;
std : : atomic_bool playback_ready ;
2024-03-23 18:41:26 -07:00
std : : atomic_bool speed_changed ;
std : : atomic_bool tempo_changed ;
std : : atomic_bool pitch_changed ;
std : : atomic_bool pause_changed ;
2024-04-13 12:56:39 -07:00
std : : atomic_bool load_requested ;
std : : atomic_bool load_finished ;
std : : atomic_bool stream_changed ;
2023-04-24 13:45:06 -07:00
std : : mutex flag_mutex ;
2023-12-22 14:13:48 -08:00
std : : mutex error_mutex ;
2023-04-24 13:45:06 -07:00
std : : thread thread ;
double position ;
double length ;
bool paused ;
2023-07-15 14:52:49 -07:00
Uint8 * buf ;
2023-07-10 12:45:24 -07:00
size_t bufsize ;
2023-07-15 14:52:49 -07:00
SDL_AudioDeviceID device ;
SoundTouch * st ;
SDL_AudioSpec spec ;
2024-08-08 13:12:37 -07:00
SDL_AudioSpec backend_spec ;
SDL_AudioStream * sdl_stream = nullptr ;
2023-07-15 14:52:49 -07:00
void SDLCallbackInner ( Uint8 * stream , int len ) ;
static void SDLCallback ( void * userdata , Uint8 * stream , int len ) ;
2024-08-08 13:12:37 -07:00
void Load ( const char * file , int idx ) ;
void Unload ( ) ;
2024-04-10 18:00:19 -07:00
VGMSTREAM * stream ;
2024-04-24 09:59:51 -07:00
Mix_Music * music ;
2024-04-14 13:25:49 -07:00
std : : vector < PlaybackStream > streams ;
2024-04-13 12:56:39 -07:00
std : : mutex stream_list_mutex ;
2024-04-10 18:00:19 -07:00
double real_volume = 1.0 ;
2023-04-24 13:45:06 -07:00
void ThreadFunc ( ) ;
2023-09-03 11:54:07 -07:00
void UpdateST ( ) ;
double GetMaxSeconds ( ) ;
2023-12-22 14:13:48 -08:00
queue < std : : string > errors ;
2024-03-23 18:41:26 -07:00
std : : mutex current_file_mutex ;
2024-04-13 12:56:39 -07:00
int current_stream ;
2024-03-23 18:41:26 -07:00
std : : optional < std : : string > current_file ;
2024-03-26 18:39:02 -07:00
std : : optional < std : : string > current_title ;
2024-03-23 18:41:26 -07:00
float prev_pitch , prev_speed , prev_tempo ;
2024-08-08 13:12:37 -07:00
FILE_TYPE * file ;
2024-09-28 10:31:06 -07:00
bool initial_render = false ;
2023-04-24 13:45:06 -07:00
public :
2024-03-26 18:39:02 -07:00
PlaybackInstance ( ) ;
~ PlaybackInstance ( ) override ;
std : : optional < std : : string > get_current_file ( ) override ;
std : : optional < std : : string > get_current_title ( ) override ;
2024-04-13 12:56:39 -07:00
int get_current_stream ( ) override ;
2024-04-09 10:15:05 -07:00
inline bool is_proxy ( ) override {
return false ;
}
2024-04-14 13:25:49 -07:00
std : : vector < PlaybackStream > get_streams ( ) override ;
2024-04-13 12:56:39 -07:00
void play_stream ( int idx ) override ;
void Load ( std : : string filePath ) override ;
2024-03-26 18:39:02 -07:00
double GetPosition ( ) override ;
double GetLength ( ) override ;
void Seek ( double position ) override ;
2024-04-13 12:56:39 -07:00
void Start ( std : : string filePath , int streamIdx = 0 ) override ;
2024-03-26 18:39:02 -07:00
bool IsPaused ( ) override ;
void Pause ( ) override ;
void Stop ( ) override ;
2023-04-24 13:45:06 -07:00
void Update ( ) ;
2024-03-26 18:39:02 -07:00
bool IsStopped ( ) override ;
void SetTempo ( float tempo ) override ;
void SetPitch ( float pitch ) override ;
void SetSpeed ( float speed ) override ;
void SetVolume ( float volume ) override ;
float GetTempo ( ) override ;
float GetPitch ( ) override ;
float GetSpeed ( ) override ;
float GetVolume ( ) override ;
2024-04-24 09:59:51 -07:00
void InitLoopFunction ( ) override ;
void DeinitLoopFunction ( ) override ;
void LoopFunction ( ) override ;
2023-04-24 13:45:06 -07:00
float volume ;
float speed ;
2023-07-15 14:52:49 -07:00
float tempo ;
float pitch ;
2023-12-22 14:13:48 -08:00
float MaxSeconds = 100.0 ;
float MaxSpeed = 4.0 ;
float MaxPitch = 4.0 ;
float MaxTempo = 4.0 ;
float MinSpeed = 0.25 ;
float MinPitch = 0.25 ;
float MinTempo = 0.25 ;
2024-08-08 13:12:37 -07:00
} ;