#pragma once #include extern "C" { #include } #ifdef __ANDROID__ #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace soundtouch; using std::span; using std::optional; using std::vector; using std::queue; using std::deque; enum { /// @brief No signals have occurred. PlaybackSignalNone = 0, /// @brief The file was changed. Recheck the properties of the file because they are likely different. PlaybackSignalFileChanged = 1 << 0, /// @brief The speed was changed. PlaybackSignalSpeedChanged = 1 << 1, /// @brief The speed was changed. PlaybackSignalTempoChanged = 1 << 2, /// @brief The speed was changed. PlaybackSignalPitchChanged = 1 << 3, /// @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. PlaybackSignalPaused = 1 << 4, /// @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. PlaybackSignalResumed = 1 << 5, /// @brief Playback was stopped entirely. If @ref PlaybackSignalStarted has also been signalled, call @ref Playback::IsStopped to find out if playback is currently playing. PlaybackSignalStopped = 1 << 6, /// @brief An error occurred and playback has likely (but not necessarily) stopped. Call @ref Playback::GetError for details. PlaybackSignalErrorOccurred = 1 << 7, /// @brief Playback was seeked by the @ref Playback::Seek function PlaybackSignalSeeked = 1 << 8, /// @brief Playback has started. If @ref PlaybackSignalStopped has also been signalled, call @ref Playback::IsStopped to find out if playback is currently playing. PlaybackSignalStarted = 1 << 9 }; struct PlaybackStream { double length; std::string name; int id; }; /// @brief Playback handler base class. class Playback { protected: friend class DBusAPI; /// @brief The handle-specific signal state. std::map signals_occurred; /// @brief The global error state for initializing handle-specific state. std::vector errors; /// @brief The handle-specific error state. std::map> 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); public: inline virtual bool is_proxy() { return false; } inline Playback() {}; inline virtual ~Playback() {} /// @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. inline virtual std::optional get_current_file() { return {}; } /// @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. inline virtual std::optional get_current_title() { return {}; } inline virtual int get_current_stream() { return 0; } inline virtual std::vector get_streams() { std::vector output; return output; } inline virtual void play_stream(int idx) {} /// @brief Gets the position of the playing file. inline virtual double GetPosition() { return 0.0; } /// @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. inline virtual double GetLength() { return 0.0; } /// @brief Sets the playback position into the current file. inline virtual void Seek(double position) {} /// @brief Loads a new file. inline virtual void Load(std::string filePath) {} /// @brief Plays a new file. inline virtual void Start(std::string filePath, int streamIdx = 0) {} /// @brief Checks whether or not the file is paused. /// @returns true if playback is paused, false otherwise inline virtual bool IsPaused() { return true; } /// @brief Toggles the pause state of the playback. inline virtual void Pause() {} /// @brief Stops all playback. inline virtual void Stop() {} /// @brief Checks whether or not playback is stopped. /// @returns true if playback is stopped, false otherwise. inline virtual bool IsStopped() { return true; } /// @brief Sets the tempo multiplier of the playback. inline virtual void SetTempo(float tempo) {} /// @brief Sets the speed multiplier (Both speed and pitch, but this is a separate property) of the playback. inline virtual void SetSpeed(float speed) {} /// @brief Sets the pitch multiplier of the playback inline virtual void SetPitch(float pitch) {} /// @brief Sets the volume of the playback, as a percentage inline virtual void SetVolume(float volume) {} /// @brief Gets the tempo multiplier of the playback. inline virtual float GetTempo() { return 1.0; } /// @brief Gets the speed multiplier (Both speed and pitch, but this is a separate property) of the playback. inline virtual float GetSpeed() { return 1.0; } /// @brief Gets the pitch multiplier of the playback. inline virtual float GetPitch() { return 1.0; } /// @brief Gets the volume of the playback, as a percentage inline virtual float GetVolume() { return 1.0; } /// @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 GetError(void *handle) { std::optional 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; } /// @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; } /// @brief Helper function to set the paused status via the @ref Playback::IsPaused and @ref Playback::Pause APIs. /// @param paused The new pause state. inline virtual void SetPaused(bool paused) { if (IsPaused() != paused) { Pause(); } } 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(); } } static Playback *Create(bool *daemon_found, bool daemon = false); }; class DBusAPISender; class PlaybackInstance : public Playback #ifdef __ANDROID__ , public oboe::AudioStreamDataCallback #endif { private: #ifdef __ANDROID__ std::shared_ptr ostream; public: oboe::DataCallbackResult onAudioReady( oboe::AudioStream *audioStream, void *audioData, int32_t numFrames) override; private: #endif std::string filePath; std::atomic_bool running; std::atomic_bool file_changed; std::atomic_bool seeking; std::atomic_bool update; std::atomic_bool restart; std::atomic_bool playback_ready; std::atomic_bool speed_changed; std::atomic_bool tempo_changed; std::atomic_bool pitch_changed; std::atomic_bool pause_changed; std::atomic_bool load_requested; std::atomic_bool load_finished; std::atomic_bool stream_changed; std::mutex flag_mutex; std::mutex error_mutex; std::thread thread; double position; double length; bool paused; Uint8* buf; size_t bufsize; Mix_CommonMixer_t general_mixer; SDL_AudioDeviceID device; SoundTouch *st; SDL_AudioSpec spec; SDL_AudioStream *sdl_stream; /// @brief A fake SDL_AudioSpec used to trick SDL Mixer X into allocating a bigger buffer. SDL_AudioSpec fakespec; SDL_AudioSpec vgmstream_spec; void SDLCallbackInner(Uint8 *stream, int len); static void SDLCallback(void *userdata, Uint8 *stream, int len); Mix_Music *LoadMix(const char* file); VGMSTREAM *LoadVgm(const char *file, int idx = 0); void UnloadMix(Mix_Music* music); void UnloadVgm(VGMSTREAM *stream); VGMSTREAM *stream; Mix_Music *music; std::vector streams; std::mutex stream_list_mutex; double real_volume = 1.0; void ThreadFunc(); void UpdateST(); double GetMaxSeconds(); queue errors; std::mutex current_file_mutex; int current_stream; std::optional current_file; std::optional current_title; float prev_pitch, prev_speed, prev_tempo; public: PlaybackInstance(); ~PlaybackInstance() override; std::optional get_current_file() override; std::optional get_current_title() override; int get_current_stream() override; inline bool is_proxy() override { return false; } std::vector get_streams() override; void play_stream(int idx) override; void Load(std::string filePath) override; double GetPosition() override; double GetLength() override; void Seek(double position) override; void Start(std::string filePath, int streamIdx = 0) override; bool IsPaused() override; void Pause() override; void Stop() override; void Update(); 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; void InitLoopFunction() override; void DeinitLoopFunction() override; void LoopFunction() override; float volume; float speed; float tempo; float pitch; 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; };