Split last.fm scrobbling and play count calculation, closes #5771 (#6179)

Also add a configurable option to increment the play count if song has played for a shorter duration.
This commit is contained in:
Victor Parmar 2018-10-20 13:57:49 +02:00 committed by John Maguire
parent 9eb92ee2b5
commit f4d84bc05a
6 changed files with 174 additions and 31 deletions

View File

@ -35,37 +35,38 @@
#include <QtConcurrentRun>
#include <QtDebug>
#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;
}

View File

@ -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_;
};

View File

@ -15,13 +15,15 @@
along with Clementine. If not, see <http://www.gnu.org/licenses/>.
*/
#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 <QFileDialog>
#include <QInputDialog>
@ -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);
}

View File

@ -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");

View File

@ -428,6 +428,32 @@
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_play_count">
<property name="title">
<string>When calculating play counts, use</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QRadioButton" name="b_play_count_normal_duration">
<property name="text">
<string>Normal duration (at least 4 minutes or half the track length)</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="b_play_count_short_duration">
<property name="text">
<string>Short duration (at least 1 minute or half the track length)</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacer_2">
<property name="orientation">

View File

@ -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