diff --git a/src/playlist/playlist.cpp b/src/playlist/playlist.cpp index 3b8a5390a..07848e643 100644 --- a/src/playlist/playlist.cpp +++ b/src/playlist/playlist.cpp @@ -35,37 +35,38 @@ #include #include +#include "core/application.h" +#include "core/closure.h" +#include "core/logging.h" +#include "core/player.h" +#include "core/qhash_qurl.h" +#include "core/tagreaderclient.h" +#include "core/timeconstants.h" +#include "internet/core/internetmimedata.h" +#include "internet/core/internetmodel.h" +#include "internet/core/internetplaylistitem.h" +#include "internet/core/internetsongmimedata.h" +#include "internet/internetradio/savedradio.h" +#include "internet/jamendo/jamendoplaylistitem.h" +#include "internet/jamendo/jamendoservice.h" +#include "internet/magnatune/magnatuneplaylistitem.h" +#include "internet/magnatune/magnatuneservice.h" +#include "library/library.h" +#include "library/librarybackend.h" +#include "library/librarymodel.h" +#include "library/libraryplaylistitem.h" #include "playlistbackend.h" #include "playlistfilter.h" #include "playlistitemmimedata.h" #include "playlistundocommands.h" #include "playlistview.h" #include "queue.h" -#include "songloaderinserter.h" -#include "songmimedata.h" -#include "songplaylistitem.h" -#include "core/application.h" -#include "core/closure.h" -#include "core/logging.h" -#include "core/qhash_qurl.h" -#include "core/tagreaderclient.h" -#include "core/timeconstants.h" -#include "internet/jamendo/jamendoplaylistitem.h" -#include "internet/jamendo/jamendoservice.h" -#include "internet/magnatune/magnatuneplaylistitem.h" -#include "internet/magnatune/magnatuneservice.h" -#include "internet/core/internetmimedata.h" -#include "internet/core/internetmodel.h" -#include "internet/core/internetplaylistitem.h" -#include "internet/core/internetsongmimedata.h" -#include "internet/internetradio/savedradio.h" -#include "library/library.h" -#include "library/librarybackend.h" -#include "library/librarymodel.h" -#include "library/libraryplaylistitem.h" #include "smartplaylists/generator.h" #include "smartplaylists/generatorinserter.h" #include "smartplaylists/generatormimedata.h" +#include "songloaderinserter.h" +#include "songmimedata.h" +#include "songplaylistitem.h" using std::placeholders::_1; using std::placeholders::_2; @@ -156,6 +157,22 @@ Playlist::Playlist(PlaylistBackend* backend, TaskManager* task_manager, connect(queue_, SIGNAL(layoutChanged()), SLOT(QueueLayoutChanged())); column_alignments_ = PlaylistView::DefaultColumnAlignment(); + + min_play_count_point_nsecs_ = (31ll * kNsecPerSec); // 30 seconds + + QSettings settings; + settings.beginGroup(Player::kSettingsGroup); + + if (settings.value("play_count_short_duration").toBool()) { + max_play_count_point_nsecs_ = (60ll * kNsecPerSec); // 1 minute + } else { + max_play_count_point_nsecs_ = (240ll * kNsecPerSec); // 4 minutes + } + + settings.endGroup(); + + qLog(Debug) << "k_max_scrobble_point" + << (max_play_count_point_nsecs_ / kNsecPerSec); } Playlist::~Playlist() { @@ -1803,6 +1820,15 @@ Song Playlist::current_item_metadata() const { return current_item()->Metadata(); } +/** + * Last.fm defines a track to be scrobbled when + * - the track is longer than 30 seconds + * - the track has been played for at least half its duration, or for 4 minutes + * (whichever occurs earlier.) + * + * If you seek a track, the scrobble point is recalculated from the point seeked + * to (as 50% or 4 minutes). + */ void Playlist::UpdateScrobblePoint(qint64 seek_point_nanosec) { const qint64 length = current_item_metadata().length_nanosec(); @@ -1825,6 +1851,41 @@ void Playlist::UpdateScrobblePoint(qint64 seek_point_nanosec) { } set_lastfm_status(LastFM_New); + UpdatePlayCountPoint(seek_point_nanosec); +} + +/** + * Initially the play count tracking and scrobbling went hand in hand. + * However, it is is possible that someone's preferences for tracking play + * counts are more relaxed than that of scrobbling. For those cases, we use + * the following algorithm to track whether a song should increment it's play + * count or not. + * + * Note that that this is very similar to the scrobbling algorithm with the only + * difference that the requirement of 4 mins worth of listening is now + * configurable via the `max_play_count_point_nsecs_` parameter. + */ +void Playlist::UpdatePlayCountPoint(qint64 seek_point_nanosec) { + const qint64 length = current_item_metadata().length_nanosec(); + + if (seek_point_nanosec == 0) { + if (length == 0) { + play_count_point_ = max_play_count_point_nsecs_; // 4 minutes + } else { + play_count_point_ = qBound(min_play_count_point_nsecs_, length / 2, + max_play_count_point_nsecs_); + } + } else { + if (length == 0) { + play_count_point_ = seek_point_nanosec + max_play_count_point_nsecs_; + } else { + play_count_point_ = + qBound(seek_point_nanosec + min_play_count_point_nsecs_, + seek_point_nanosec + (length / 2), + seek_point_nanosec + max_play_count_point_nsecs_); + } + } + have_incremented_playcount_ = false; } diff --git a/src/playlist/playlist.h b/src/playlist/playlist.h index 0899238e6..a06b69cc1 100644 --- a/src/playlist/playlist.h +++ b/src/playlist/playlist.h @@ -236,6 +236,16 @@ class Playlist : public QAbstractListModel { void set_have_incremented_playcount() { have_incremented_playcount_ = true; } void UpdateScrobblePoint(qint64 seek_point_nanosec = 0); + // play count tracking + qint64 play_count_point_nanosec() const { return play_count_point_; } + void set_max_play_count_point_nsecs(qint64 max_play_count_point_nsecs) { + max_play_count_point_nsecs_ = max_play_count_point_nsecs; + } + qint64 get_max_play_count_point_nsecs() const { + return max_play_count_point_nsecs_; + } + void UpdatePlayCountPoint(qint64 seek_point_nanosec = 0); + // Changing the playlist void InsertItems(const PlaylistItemList& items, int pos = -1, bool play_now = false, bool enqueue = false, @@ -440,6 +450,8 @@ signals: LastFMStatus lastfm_status_; bool have_incremented_playcount_; + qint64 play_count_point_; + PlaylistSequence* playlist_sequence_; // Hack to stop QTreeView::setModel sorting the playlist @@ -454,6 +466,9 @@ signals: QString special_type_; + qint64 min_play_count_point_nsecs_; + qint64 max_play_count_point_nsecs_; + // Cancel async restore if songs are already replaced bool cancel_restore_; }; diff --git a/src/playlist/playlistcontainer.cpp b/src/playlist/playlistcontainer.cpp index 58f1ad2d6..8d649f2db 100644 --- a/src/playlist/playlistcontainer.cpp +++ b/src/playlist/playlistcontainer.cpp @@ -15,13 +15,15 @@ along with Clementine. If not, see . */ +#include "core/appearance.h" +#include "core/logging.h" +#include "core/player.h" +#include "core/timeconstants.h" #include "playlistcontainer.h" #include "playlistmanager.h" -#include "ui_playlistcontainer.h" -#include "core/logging.h" -#include "core/appearance.h" #include "playlistparsers/playlistparser.h" #include "ui/iconloader.h" +#include "ui_playlistcontainer.h" #include #include @@ -464,4 +466,16 @@ void PlaylistContainer::ReloadSettings() { settings.beginGroup(Appearance::kSettingsGroup); bool hide_toolbar = settings.value("b_hide_filter_toolbar", false).toBool(); ui_->toolbar->setVisible(!hide_toolbar); + settings.endGroup(); + + settings.beginGroup(Player::kSettingsGroup); + if (settings.value("play_count_short_duration").toBool()) { + playlist_->set_max_play_count_point_nsecs(60ll * kNsecPerSec); + } else { + playlist_->set_max_play_count_point_nsecs(240ll * kNsecPerSec); + } + settings.endGroup(); + + qLog(Debug) << "new max scrobble point:" + << (playlist_->get_max_play_count_point_nsecs() / kNsecPerSec); } diff --git a/src/ui/behavioursettingspage.cpp b/src/ui/behavioursettingspage.cpp index f1654dd9f..bb5a6c17a 100644 --- a/src/ui/behavioursettingspage.cpp +++ b/src/ui/behavioursettingspage.cpp @@ -172,6 +172,14 @@ void BehaviourSettingsPage::Load() { s.value("menu_previousmode", Player::PreviousBehaviour_DontRestart) .toInt())); ui_->seek_step_sec->setValue(s.value("seek_step_sec", 10).toInt()); + + if (s.value("play_count_short_duration", false).toBool()) { + ui_->b_play_count_short_duration->setChecked(true); + ui_->b_play_count_normal_duration->setChecked(false); + } else { + ui_->b_play_count_short_duration->setChecked(false); + ui_->b_play_count_normal_duration->setChecked(true); + } s.endGroup(); s.beginGroup("General"); @@ -277,6 +285,13 @@ void BehaviourSettingsPage::Save() { ui_->stop_play_if_fail_->isChecked()); s.setValue("menu_previousmode", menu_previousmode); s.setValue("seek_step_sec", ui_->seek_step_sec->value()); + + if (ui_->b_play_count_short_duration->isChecked()) { + s.setValue("play_count_short_duration", true); + } else { + s.setValue("play_count_short_duration", false); + } + s.endGroup(); s.beginGroup("General"); diff --git a/src/ui/behavioursettingspage.ui b/src/ui/behavioursettingspage.ui index 88b42e77a..2005d707c 100644 --- a/src/ui/behavioursettingspage.ui +++ b/src/ui/behavioursettingspage.ui @@ -428,6 +428,32 @@ + + + + When calculating play counts, use + + + + + + Normal duration (at least 4 minutes or half the track length) + + + true + + + + + + + Short duration (at least 1 minute or half the track length) + + + + + + diff --git a/src/ui/mainwindow.cpp b/src/ui/mainwindow.cpp index 30348ca0a..e4b2bb31e 100644 --- a/src/ui/mainwindow.cpp +++ b/src/ui/mainwindow.cpp @@ -1468,6 +1468,9 @@ void MainWindow::Seeked(qlonglong microseconds) { if (ui_->action_toggle_scrobbling->isVisible()) SetToggleScrobblingIcon(true); } +/** + * Update track position, tray icon, playcount + */ void MainWindow::UpdateTrackPosition() { // Track position in seconds Playlist* playlist = app_->playlist_manager()->active(); @@ -1477,29 +1480,32 @@ void MainWindow::UpdateTrackPosition() { float(app_->player()->engine()->position_nanosec()) / kNsecPerSec + 0.5); const int length = app_->player()->engine()->length_nanosec() / kNsecPerSec; const int scrobble_point = playlist->scrobble_point_nanosec() / kNsecPerSec; + const int play_count_point = + playlist->play_count_point_nanosec() / kNsecPerSec; if (length <= 0) { // Probably a stream that we don't know the length of return; } + #ifdef HAVE_LIBLASTFM + // Time to scrobble? const bool last_fm_enabled = ui_->action_toggle_scrobbling->isVisible() && app_->scrobbler()->IsScrobblingEnabled() && app_->scrobbler()->IsAuthenticated(); -#endif - // Time to scrobble? if (position >= scrobble_point) { if (playlist->get_lastfm_status() == Playlist::LastFM_New) { -#ifdef HAVE_LIBLASTFM if (app_->scrobbler()->IsScrobblingEnabled() && app_->scrobbler()->IsAuthenticated()) { qLog(Info) << "Scrobbling at" << scrobble_point; app_->scrobbler()->Scrobble(); } -#endif } + } +#endif + if (position >= play_count_point) { // Update the play count for the song if it's from the library if (!playlist->have_incremented_playcount() && item->IsLocalLibraryItem() && item->Metadata().id() != -1 && @@ -1519,8 +1525,14 @@ void MainWindow::UpdateTrackPosition() { // Update the tray icon every 10 seconds if (position % 10 == 0) { - qLog(Debug) << "position" << position << "scrobble point" << scrobble_point - << "status" << playlist->get_lastfm_status(); + qLog(Debug) << "position:" << position + << ", scrobble point:" << scrobble_point + << ", lastfm status:" << playlist->get_lastfm_status() + << ", play count point:" << play_count_point + << ", is local libary item:" << item->IsLocalLibraryItem() + << ", playlist have incremented playcount: " + << playlist->have_incremented_playcount(); + if (tray_icon_) tray_icon_->SetProgress(double(position) / length * 100); // if we're waiting for the scrobble point, update the icon