#pragma once #include "aoapplication.h" #include #include class AOMusicPlayer : public QObject { Q_OBJECT public: // 0 = music // 1 = ambience static constexpr int STREAM_COUNT = 2; explicit AOMusicPlayer(AOApplication *ao_app); ~AOMusicPlayer(); void setMuted(bool enabled); QString playStream(QString song, int streamId, bool loopEnabled, int effectFlags); void setStreamVolume(int value, int streamId); void setStreamLooping(bool enabled, int streamId); void play_from_url(const QString &url, int id, bool looping, int flags); void play_from_file(const QString &path, int id, bool looping, int flags); private: struct Stream { ma_sound sound; QByteArray buffer; ma_decoder decoder; // I've had use-after-free crashes on `stop` due to QNetworkReply's lifetime // being unclear (specifically, calling `abort` on it). QPointer is a // bandaid fix. QPointer reply; bool has_decoder; enum { standby, playing, } state = standby; }; // Default flags for music: STREAM decodes incrementally in 2-second chunks, // the other two disable 3D positioning and Doppler effect. const ma_uint32 m_flags = MA_SOUND_FLAG_STREAM | MA_SOUND_FLAG_NO_SPATIALIZATION | MA_SOUND_FLAG_NO_PITCH; AOApplication *ao_app; float m_volume[STREAM_COUNT]{}; bool m_muted = false; Stream m_stream[STREAM_COUNT]{}; ma_pcm_rb m_audio_ring; quint32 m_loop_start[STREAM_COUNT]{}; quint32 m_loop_end[STREAM_COUNT]{}; bool ensureValidStreamId(int streamId); void on_url_download_finished(int id, QNetworkReply *reply, bool looping); void start_playback(int id); void stop(int id); signals: void track_ready(QString label); };