diff --git a/CMakeLists.txt b/CMakeLists.txt index b6e38ba02f942a891a338dd7a3b40a5455c195c8..fada86a5e85124ce6b52bbdc88a6f8c9fd7d8dae 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,6 +9,6 @@ find_package(Wt REQUIRED) find_package(Groove REQUIRED) add_executable(arbitrateor src/main.cpp src/WebInterface.cpp src/GroovePlayer.cpp src/ui/LoginInterface.cpp src/ui/PlayerInterface.cpp) -target_link_libraries(arbitrateor ${Wt_LIBRARIES} ${GROOVE_LIBRARY} ${GROOVE_FINGERPRINT_LIBRARY} pthread stdc++fs) #TODO get threading links based on platform. Remove gcc experimental fs when official c++17 exists. +target_link_libraries(arbitrateor ${Wt_LIBRARIES} ${GROOVE_LIBRARY} ${GROOVE_FINGERPRINT_LIBRARY} ${GROOVE_PLAYER_LIBRARY} pthread stdc++fs) #TODO get threading links based on platform. Remove gcc experimental fs when official c++17 exists. install(TARGETS arbitrateor RUNTIME DESTINATION bin) diff --git a/cmake/FindGroove.cmake b/cmake/FindGroove.cmake index 21f15c337314ecc646018cd63d207448b6129afd..9161560a583e69e75b604d4c0cff3e76944bcea3 100644 --- a/cmake/FindGroove.cmake +++ b/cmake/FindGroove.cmake @@ -6,13 +6,15 @@ # GROOVE_INCLUDE_DIR # GROOVE_LIBRARY # GROOVE_FINGERPRINT_LIBRARY +# GROOVE_PLAYER_LIBRARY find_path(GROOVE_INCLUDE_DIR NAMES groove/groove.h) find_library(GROOVE_LIBRARY NAMES groove) find_library(GROOVE_FINGERPRINT_LIBRARY NAMES groovefingerprinter) +find_library(GROOVE_PLAYER_LIBRARY NAMES grooveplayer) include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(GROOVE DEFAULT_MSG GROOVE_LIBRARY GROOVE_FINGERPRINT_LIBRARY GROOVE_INCLUDE_DIR) +find_package_handle_standard_args(GROOVE DEFAULT_MSG GROOVE_LIBRARY GROOVE_FINGERPRINT_LIBRARY GROOVE_PLAYER_LIBRARY GROOVE_INCLUDE_DIR) -mark_as_advanced(GROOVE_INCLUDE_DIR GROOVE_LIBRARY GROOVE_FINGERPRINT_LIBRARY) +mark_as_advanced(GROOVE_INCLUDE_DIR GROOVE_LIBRARY GROOVE_FINGERPRINT_LIBRARY GROOVE_PLAYER_LIBRARY) diff --git a/src/GroovePlayer.cpp b/src/GroovePlayer.cpp index 6cef18d2ecfd40924b3b68968ac3a5eab49bdea9..7fcba4c287b305f16c479b9249bdf74ac696af67 100644 --- a/src/GroovePlayer.cpp +++ b/src/GroovePlayer.cpp @@ -50,16 +50,103 @@ GroovePlayer::GroovePlayer(std::string dbFile) : sqliteConnection(dbFile) grooveEvents = new std::thread(grooveEventLoop); } -std::vector GroovePlayer::getNextVoteBatch() +std::list GroovePlayer::getNextVoteBatch() { - //TODO + /** + * This method will attempt to pick 3 tracks that will be up for selection next. + * + * The first track should be from the same album or artist. + * The second track should be from the same genre. + * The third track should be from something in a different genre. + * + * What is picked inside those requirements is a mix of random and TODO:based on past user actions(33% of time). + * + * The third track will be replaced by a recent request item if one exists. + */ + + std::list selectedTracks; + + Wt::Dbo::Transaction transaction(getInstance()->sqlSession); + //First make sure there are at least 3 tracks to suggest. + int trackCount = getInstance()->sqlSession.query("select count(fingerprint) from tracks"); + if(trackCount < 3) + { + //Return empty list to show there are not enough tracks to allow voting. + return selectedTracks; + } + + //Determine first track + int trackAlbumCount = getInstance()->sqlSession.query("select count(fingerprint) from tracks").where("album = ?").bind(getCurrentTrack()->trackAlbumName); + int trackArtistCount = getInstance()->sqlSession.query("select count(fingerprint) from tracks").where("artist = ?").bind(getCurrentTrack()->trackArtistName); + int computerSlightOfHand = rand() % 10 + 1; + if(trackAlbumCount > 0 && computerSlightOfHand > 3) + { + //Pick item from album + Wt::Dbo::ptr eligibleTrack = getInstance()->sqlSession.find().where("album = ?").offset(rand() % trackAlbumCount).bind(getCurrentTrack()->trackAlbumName); + selectedTracks.push_back(eligibleTrack.get()); + } + else if(trackArtistCount > 0) + { + //Pick item from artist + Wt::Dbo::ptr eligibleTrack = getInstance()->sqlSession.find().where("artist = ?").offset(rand() % trackArtistCount).bind(getCurrentTrack()->trackArtistName); + selectedTracks.push_back(eligibleTrack.get()); + } + + //Determine second track + int trackGenreCount = getInstance()->sqlSession.query("select count(fingerprint) from tracks").where("genre = ?").bind(getCurrentTrack()->trackGenre); + if(trackGenreCount > 0) + { + //Pick item from genre + Wt::Dbo::ptr eligibleTrack = getInstance()->sqlSession.find().where("genre = ?").offset(rand() % trackGenreCount).bind(getCurrentTrack()->trackGenre); + selectedTracks.push_back(eligibleTrack.get()); + } + + //Determine third track + int trackNotGenreCount = getInstance()->sqlSession.query("select count(fingerprint) from tracks").where("genre != ?").bind(getCurrentTrack()->trackGenre); + if(requestQueue.size() > 0) + { + //There's a request. Pick one up front and put as third item. + selectedTracks.push_back(requestQueue.front()); + } + else if(trackNotGenreCount > 0) + { + //Pick from other genre + Wt::Dbo::ptr eligibleTrack = getInstance()->sqlSession.find().where("genre != ?").offset(rand() % trackNotGenreCount).bind(getCurrentTrack()->trackGenre); + selectedTracks.push_back(eligibleTrack.get()); + } + else + { + //Pick randomly + Wt::Dbo::ptr eligibleTrack = getInstance()->sqlSession.find().offset(rand() % trackCount); + selectedTracks.push_back(eligibleTrack.get()); + } + + return selectedTracks; } void GroovePlayer::grooveEventLoop() { + /** + * On first boot, random track that has been played before will start(or random if no track has been played before). + * When voting ends, if no vote was cast, the first will be picked 60% of the time with second being 30% of the time and third being picked 10% of the time. + * Exception is if third track is a request, then it is picked. If another song is voted over this, it will be put in back of request queue. + */ //TODO } +GroovePlayer::PlayerEvents GroovePlayer::getNextPlayerEvent() +{ + //TODO +} + +Wt::Dbo::ptr GroovePlayer::getCurrentTrack() +{ + Wt::Dbo::Transaction transaction(getInstance()->sqlSession); + Wt::Dbo::ptr track = getInstance()->sqlSession.find().where("path = ?").bind(currentItem->file->filename); + return track; +} + + bool GroovePlayer::addFileToTrackDBIfTagged(std::filesystem::path file) { //Now check if tags exist and put into DB. diff --git a/src/GroovePlayer.h b/src/GroovePlayer.h index ac66b5f85bbdbf01c3d11d22c667ea334ff58a48..71939d382d01ddac153452cc65d44a7dffd632b7 100644 --- a/src/GroovePlayer.h +++ b/src/GroovePlayer.h @@ -22,7 +22,7 @@ #include #include -#include +#include #include #include "db/User.h" #include "db/AudioTrack.h" @@ -44,19 +44,28 @@ public: GroovePlayer(GroovePlayer const&) = delete; void operator=(GroovePlayer const&) = delete; private: + enum PlayerEvents {NOTHING, GROOVE_NOWPLAYING, VOTING_ENDED, VOTE_CAST, PLAYING_PAUSED, PLAYING_RESUMED, SKIP_REQUESTED, SKIP_VOTE_CAST, SKIP_VOTING_ENDED}; + GroovePlayer(std::string dbFile); Wt::Dbo::backend::Sqlite3 sqliteConnection; Wt::Dbo::Session sqlSession; - std::vector requestQueue; + std::list requestQueue; + std::list lastInternalEvents; + GroovePlaylistItem* currentItem; + + std::thread* grooveEvents; static void grooveEventLoop(); - std::vector getNextVoteBatch(); + std::list getNextVoteBatch(); std::thread* grooveAudioScanner; static void grooveAudioScannerLoop(); + PlayerEvents getNextPlayerEvent(); static bool addFileToTrackDBIfTagged(std::filesystem::path file); static void removeOrphanedTracks(); + Wt::Dbo::ptr getCurrentTrack(); + }; #endif // GROOVEPLAYER_H