From 80bffc0fddbd731aec4cdbc382e672b191bddc22 Mon Sep 17 00:00:00 2001 From: Florian Egger Date: Sat, 20 Jun 2026 15:19:34 +0200 Subject: [PATCH] Add Database and refactor player/playback Add Database class with SQLite fetch/fetchAlbum methods. Refactor PlaybackEngine (data callback, seek, getPosition, decoder lifecycle) and Player API (PlaybackEngine member, queue handling, PlayerLocal). Update Metadata::Track fields to include artist/title/album/trackNr/length. --- include/Database.h | 19 ++++++++ include/Metadata.h | 7 ++- include/PlaybackEngine.h | 36 ++++++++------- include/Player.h | 79 +++++++++++++++------------------ include/PlayerEngine.h | 9 ++++ include/PlayerLocal.h | 17 +++++++ src/Database.cpp | 73 ++++++++++++++++++++++++++++++ src/PlaybackEngine.cpp | 96 ++++++++++++++++++++++------------------ src/Player.cpp | 8 +++- src/PlayerEngine.cpp | 38 +++++++++++++++- src/PlayerLocal.cpp | 37 ++++++++++++++++ 11 files changed, 315 insertions(+), 104 deletions(-) create mode 100644 include/Database.h create mode 100644 include/PlayerLocal.h create mode 100644 src/Database.cpp create mode 100644 src/PlayerLocal.cpp diff --git a/include/Database.h b/include/Database.h new file mode 100644 index 0000000..2dd35e3 --- /dev/null +++ b/include/Database.h @@ -0,0 +1,19 @@ +#pragma once + +#include "Metadata.h" + +#include + +using queue = std::vector; + +class Database { + public: + Database( const char* filename, sqlite3** db ); + ~Database(); + void fetch( const std::string& query ); + void fetchAlbum( const std::string& album ); + void addSong( ); + private: + sqlite3* db_; + sqlite3_stmt* stmnt; +}; diff --git a/include/Metadata.h b/include/Metadata.h index a888a85..1571ab7 100644 --- a/include/Metadata.h +++ b/include/Metadata.h @@ -19,5 +19,10 @@ struct Metadata { }; struct Track { - + std::string_view artist; + std::string_view title; + std::string_view album; + unsigned int trackNr; + unsigned int length; + const char *filePath; }; diff --git a/include/PlaybackEngine.h b/include/PlaybackEngine.h index 3ac0e06..bbe6253 100644 --- a/include/PlaybackEngine.h +++ b/include/PlaybackEngine.h @@ -6,35 +6,39 @@ #include struct PlaybackState { - ma_decoder decoder{}; - std::atomic finished{ false }; + ma_decoder decoder {}; + std::atomic finished { false }; }; class PlaybackEngine { -public: - PlaybackEngine(); - ~PlaybackEngine(); + public: + PlaybackEngine (); + ~PlaybackEngine (); - static void data_callback( ma_device* pDevice, void* pOutput, const void*, ma_uint32 frameCount ); - void configureDevice(); - void configureDecoder(); - void initDevice(); - void initDecoder( const char* filePath ); + static void data_callback ( ma_device *pDevice, void *pOutput, const void *, ma_uint32 frameCount ); + void configureDevice (); + void configureDecoder (); + void initDevice (); + void initDecoder ( const char *filePath ); + void uninitDecoder(); - void startPlayback(); - void stopPlayback(); - void pausePlayback() { stopPlayback(); }; - int isFinished() const; + void startPlayback (); + void stopPlayback (); + void pausePlayback () { stopPlayback(); }; + void seek ( ma_uint64 frame ); + int isFinished () const; -private: + ma_uint64 getPosition (); + private: ma_device device_; ma_format format_; unsigned int channels_; unsigned int sampleRate_; PlaybackState state; - //config + ma_uint64 cursor_; + // config ma_decoder_config decoderConfig; ma_device_config deviceConfig; }; diff --git a/include/Player.h b/include/Player.h index c30c014..4539da4 100644 --- a/include/Player.h +++ b/include/Player.h @@ -1,63 +1,56 @@ #pragma once #include "../include/Metadata.h" +#include "../include/PlaybackEngine.h" #include "../include/miniaudio.h" +#include #include #include -#include using Queue = std::vector; class Player { - public: - virtual ~Player() = delete; + public: + virtual ~Player (); - virtual void initPlayer(); - void loadFile() const; + virtual void initPlayer (); + virtual void loadFile ( Track track ); - // Transport Control - virtual void play() const; - virtual void stop() const; - virtual void pause() const; - virtual void previous() const; - virtual void next() const; + // Transport Control + virtual void play (); + virtual void stop (); + virtual void pause (); + virtual void previous (); + virtual void next (); - virtual void getPlayheadPosition() const; - virtual void setPlayheadPosition() const; + virtual ma_uint64 getPlayheadPosition (); + virtual void setPlayheadPosition () const; - // get Metadat - virtual std::string& getArtist() const; - virtual std::string& getTitle() const; - virtual std::string& getAlbum() const; - virtual unsigned int getTrackNr() const; + // get Metadat + virtual std::string &getArtist () const; + virtual std::string &getTitle () const; + virtual std::string &getAlbum () const; + virtual unsigned int getTrackNr () const; - // Player Queue - Queue getQueue() const; - Queue clearQueue(); - Queue addToQueue( Track& track ); - Queue rempveFromQueue( Track& track ); - Queue shuffleQueue(); + // Player Queue + Queue getQueue () const; + Queue clearQueue (); + void addToQueue ( Track &track ); + Queue rempveFromQueue ( Track &track ); + Queue shuffleQueue (); - private: - std::string artist_; - std::string title_; - std::string album_; - std::string trackNr_; - std::string filePath_; - Queue queue_; - - unsigned int samplerate_ = 48000; - unsigned int channels_ = 2; + protected: + PlaybackEngine PE_; + Queue queue_; + private: + std::string artist_; + std::string title_; + std::string album_; + std::string trackNr_; + std::string filePath_; -}; - -class PlayerLocal : public Player { - public: - virtual void initPlayer() override; - virtual ma_engine_config configureEngine() override; - - const virtual unsigned int getSamplerate() const; - + unsigned int samplerate_ = 48000; + unsigned int channels_ = 2; }; diff --git a/include/PlayerEngine.h b/include/PlayerEngine.h index 473b123..8fee9f3 100644 --- a/include/PlayerEngine.h +++ b/include/PlayerEngine.h @@ -11,10 +11,13 @@ class PlayerEngine { unsigned int sampleRate_; unsigned int channels_ = 2; ma_format format_ = ma_format_unknown; + ma_context context_; + public: PlayerEngine(); PlayerEngine( unsigned int sampleRate, unsigned int channels, ma_format format ); + ~PlayerEngine(); ma_device getDevice() const; unsigned int getChannels() const; unsigned int getSampleRate() const; @@ -24,4 +27,10 @@ class PlayerEngine { void setSampleRate( unsigned int sampleRate ); void setChannels( unsigned int channels ); void setFormat( ma_format format ); + + ma_device_data_proc data_callback(); + void configureDevice( ma_context& context ); + void initDevice (); + void initEngine(); + }; diff --git a/include/PlayerLocal.h b/include/PlayerLocal.h new file mode 100644 index 0000000..19d4a85 --- /dev/null +++ b/include/PlayerLocal.h @@ -0,0 +1,17 @@ +#pragma once + +#include "Player.h" + +class PlayerLocal : public Player { + public: + + void next() override; + void previous() override; + ma_uint64 getPlayheadPosition () override; + void loadFile ( Track track ) override; + void addToQueue( Track& track ); + void playQueue(); + + private: + unsigned int currentTrack_; +}; diff --git a/src/Database.cpp b/src/Database.cpp new file mode 100644 index 0000000..ae9db2f --- /dev/null +++ b/src/Database.cpp @@ -0,0 +1,73 @@ +#include "../include/Database.h" +#include "../include/Player.h" +#include +#include + +using db = Database; +using queue = std::vector; + +db::Database( const char* filename, sqlite3** db ) : db_( *db ){ + if ( sqlite3_open( filename, db ) != SQLITE_OK ) { + throw std::runtime_error( std::string ("Database connection failed!" ) ); + }; +} + +db::~Database() { + sqlite3_close( db_ ); +} + +void db::fetch( const std::string& query ) { + sqlite3_stmt* stmt; + + int rc = sqlite3_prepare_v2( db_, query.c_str(), -1, &stmt, nullptr ); + + if ( rc != SQLITE_OK ) { + throw std::runtime_error( + std::string( "Failed to prepare statement: ") + sqlite3_errmsg( db_ ) + ); + + while (( rc = sqlite3_step( stmt )) == SQLITE_ROW ) { + Track t; + Player p; + + const unsigned char* artist = sqlite3_column_text( stmnt, 0 ); + const unsigned char* title = sqlite3_column_text( stmt, 1 ); + const unsigned char* album = sqlite3_column_text( stmt, 2 ); + unsigned int trackNr = sqlite3_column_int( stmt, 3 ); + const unsigned char* filePath = sqlite3_column_text( stmt, 4 ); + + t.artist = artist ? reinterpret_cast( artist ) : ""; + t.title = title ? reinterpret_cast( title ) : ""; + t.album = album ? reinterpret_cast( album ) : ""; + t.trackNr = trackNr; + t.filePath = filePath ? reinterpret_cast( filePath ) : ""; + + p.addToQueue( t ); + + } + } + sqlite3_finalize( stmt ); +} + +void db::fetchAlbum( const std::string& album ) { + sqlite3_stmt* stmt; + const char* sql = "SELECT artist, title, album, trackNr from songs WHERE album = ? ORDER BY trackNr; "; + + if ( sqlite3_prepare_v2( db_, sql, -1, &stmt, nullptr ) != SQLITE_OK ) { + throw std::runtime_error( sqlite3_errmsg( db_ ) ); + } + + sqlite3_bind_text( stmt, 1, album.c_str(), -1, SQLITE_TRANSIENT ); + + while ( sqlite3_step( stmt ) == SQLITE_ROW ) { + Track t; + Player p; + t.artist = reinterpret_cast ( sqlite3_column_text( stmt, 0 )); + t.title = reinterpret_cast ( sqlite3_column_text( stmt, 1 )); + t.album = reinterpret_cast ( sqlite3_column_text( stmt, 1 )); + t.trackNr = sqlite3_column_int( stmt, 3 ); + + p.addToQueue( t ); + } + +} diff --git a/src/PlaybackEngine.cpp b/src/PlaybackEngine.cpp index 3a81844..da26a4e 100644 --- a/src/PlaybackEngine.cpp +++ b/src/PlaybackEngine.cpp @@ -3,61 +3,73 @@ #include #include - using PE = PlaybackEngine; -PE::PlaybackEngine(){ +PE::PlaybackEngine () { configureDecoder(); configureDevice(); } -PE::~PlaybackEngine() { - ma_device_uninit( &device_); - ma_decoder_uninit( &state.decoder ); +PE::~PlaybackEngine () { + ma_device_uninit ( &device_ ); + ma_decoder_uninit ( &state.decoder ); } - void PE::data_callback( ma_device* pDevice, void* pOutput, const void*, ma_uint32 frameCount ){ - std::unique_ptr state = std::unique_ptr ( static_cast( pDevice->pUserData )); - ma_uint64 framesRead = 0; - ma_result result = ma_decoder_read_pcm_frames( &state->decoder, pOutput, frameCount, &framesRead ); - if ( result != MA_SUCCESS || framesRead < frameCount ) { state->finished.store(true); } - }; +void PE::data_callback ( ma_device *pDevice, void *pOutput, const void *, ma_uint32 frameCount ) { + std::unique_ptr state = + std::unique_ptr ( static_cast ( pDevice->pUserData ) ); + ma_uint64 framesRead = 0; + ma_result result = ma_decoder_read_pcm_frames ( &state->decoder, pOutput, frameCount, &framesRead ); + if ( result != MA_SUCCESS || framesRead < frameCount ) { + state->finished.store ( true ); + } +}; - void PE::configureDevice() { - ma_device_config deviceConfig = ma_device_config_init( ma_device_type_playback ); +void PE::configureDevice () { + ma_device_config deviceConfig = ma_device_config_init ( ma_device_type_playback ); - deviceConfig.playback.format = state.decoder.outputFormat; - deviceConfig.playback.channels = state.decoder.outputChannels; - deviceConfig.sampleRate = state.decoder.outputSampleRate; - deviceConfig.dataCallback = PE::data_callback; - deviceConfig.pUserData = &state; - } + deviceConfig.playback.format = state.decoder.outputFormat; + deviceConfig.playback.channels = state.decoder.outputChannels; + deviceConfig.sampleRate = state.decoder.outputSampleRate; + deviceConfig.dataCallback = PE::data_callback; + deviceConfig.pUserData = &state; +} - void PE::configureDecoder(){ - ma_decoder_config decoderConfig = ma_decoder_config_init( format_, 0, sampleRate_ ); - } +void PE::configureDecoder () { ma_decoder_config decoderConfig = ma_decoder_config_init ( format_, 0, sampleRate_ ); } - void PE::initDevice(){ - if ( ma_device_init( nullptr, &deviceConfig, &device_ ) != MA_SUCCESS ) { - throw std::runtime_error( "failed to open file."); - } - } - -void PE::initDecoder( const char* filePath ) { - if ( ma_decoder_init_file( filePath, &decoderConfig, &state.decoder) != MA_SUCCESS ){ - throw std::runtime_error( "Failed to open {filePath}."); +void PE::initDevice () { + if ( ma_device_init ( nullptr, &deviceConfig, &device_ ) != MA_SUCCESS ) { + throw std::runtime_error ( "failed to open file." ); } } - void PE::startPlayback(){ - //ma_decoder_init_file( filePath, &decoderConfig, &decoder ); - if ( ma_device_start( &device_ ) != MA_SUCCESS ) { - throw std::runtime_error( "Failed to start playback device.\n"); - ma_device_uninit( &device_ ); - ma_decoder_uninit( &state.decoder ); - } - } +void PE::initDecoder ( const char *filePath ) { + if ( ma_decoder_init_file ( filePath, &decoderConfig, &state.decoder ) != MA_SUCCESS ) { + throw std::runtime_error ( "Failed to open {filePath}." ); + } +} - void PE::stopPlayback() { - ma_device_stop( &device_ ); - } +void PE::uninitDecoder() { + ma_decoder_uninit( &state.decoder ); +} + +void PE::startPlayback () { + // ma_decoder_init_file( filePath, &decoderConfig, &decoder ); + if ( ma_device_start ( &device_ ) != MA_SUCCESS ) { + throw std::runtime_error ( "Failed to start playback device.\n" ); + ma_device_uninit ( &device_ ); + ma_decoder_uninit ( &state.decoder ); + } +} + +void PE::stopPlayback () { ma_device_stop ( &device_ ); } + +void PE::seek ( ma_uint64 frame ) { + auto decoder = state.decoder; + ma_decoder_seek_to_pcm_frame ( &state.decoder, frame ); +} + +auto PE::getPosition () { + ma_decoder_get_cursor_in_pcm_frames ( &state.decoder, &cursor_ ); + return cursor_; +} diff --git a/src/Player.cpp b/src/Player.cpp index 493a421..4ec6529 100644 --- a/src/Player.cpp +++ b/src/Player.cpp @@ -1,3 +1,9 @@ #include "../include/Player.h" +#include "../include/PlaybackEngine.h" -//#include "../include/miniaudio.h" +// Transport controls +void Player::play () { PE_.startPlayback(); } +void Player::stop () { PE_.stopPlayback(); } +void Player::pause () { PE_.pausePlayback(); } + +ma_uint64 Player::getPlayheadPosition () { return PE_.getPosition(); } diff --git a/src/PlayerEngine.cpp b/src/PlayerEngine.cpp index 3f144d8..6fe45fe 100644 --- a/src/PlayerEngine.cpp +++ b/src/PlayerEngine.cpp @@ -1,9 +1,23 @@ #include "../include/PlayerEngine.h" +#include using pe = PlayerEngine; // Constructor - pe::PlayerEngine( unsigned int sampleRate, unsigned int channels, ma_format format ) : sampleRate_{ sampleRate }, channels_{ channels }, format_( format ) {} +pe::PlayerEngine(){ + configureDevice(context_ ); + initDevice(); + ma_device_start( &device_ ); +} +pe::PlayerEngine( unsigned int sampleRate, unsigned int channels, ma_format format ) : sampleRate_{ sampleRate }, channels_{ channels }, format_( format ) { + configureDevice(context_ ); + initDevice(); + ma_device_start( &device_ ); +} + + pe::~PlayerEngine(){ + ma_device_uninit( &device_ ); + } // Getters ma_device pe::getDevice() const { return device_; } @@ -16,3 +30,25 @@ using pe = PlayerEngine; void pe::setSampleRate ( unsigned int sampleRate ) { sampleRate_ = sampleRate; } void pe::setChannels( unsigned int channels ) { channels_ = channels; } void pe::setFormat( ma_format format ) { format_ = format; } + + void pe::configureDevice( ma_context& context ) { + ma_device_config conf = deviceConfig_; + conf = ma_device_config_init( ma_device_type_playback); + conf.playback.format = format_; + conf.playback.channels = channels_; + conf.sampleRate = sampleRate_; + conf.dataCallback = data_callback(); + } + void pe::initDevice() { + if( ma_device_init( NULL, &deviceConfig_, &device_ ) != MA_SUCCESS) { + throw std::runtime_error{ "Device could not be initialized." }; + } + } + + void initEngine() { + ma_result result; + ma_engine_config conf; + + conf = ma_engine_config_init(); + // TODO resource Manager + } diff --git a/src/PlayerLocal.cpp b/src/PlayerLocal.cpp new file mode 100644 index 0000000..80357e9 --- /dev/null +++ b/src/PlayerLocal.cpp @@ -0,0 +1,37 @@ +#include "../include/PlayerLocal.h" +#include "../include/Player.h" + +void Player::play () { PE_.startPlayback(); } +void Player::stop () { PE_.stopPlayback(); } +void PlayerLocal::next() { + unsigned int nextTrack = ++currentTrack_; + PE_.uninitDecoder(); + loadFile( queue_.at( ++currentTrack_)); + PE_.startPlayback(); +} +void PlayerLocal::previous() { + unsigned int previousTrack = --currentTrack_; + PE_.uninitDecoder(); + loadFile( queue_.at( --currentTrack_)); + PE_.startPlayback(); + +} + +ma_uint64 Player::getPlayheadPosition () { return PE_.getPosition(); }; + +void PlayerLocal::loadFile ( Track track ) { + const char *fp = track.filePath; + PE_.initDecoder ( fp ); +} + +void PlayerLocal::addToQueue( Track& track ) { + queue_.push_back( track ); +} + +void::PlayerLocal::playQueue() { + for ( Track track : queue_ ) { + loadFile( track ); + play(); + ++currentTrack_; + } +}