diff --git a/src/core/mainwindow.cpp b/src/core/mainwindow.cpp index cc973c993..ea6b8c970 100644 --- a/src/core/mainwindow.cpp +++ b/src/core/mainwindow.cpp @@ -510,6 +510,8 @@ MainWindow::MainWindow(Application *app, SystemTrayIcon *tray_icon, OSD *osd, co connect(app_->player(), SIGNAL(VolumeChanged(int)), osd_, SLOT(VolumeChanged(int))); connect(app_->player(), SIGNAL(VolumeChanged(int)), ui_->volume, SLOT(setValue(int))); connect(app_->player(), SIGNAL(ForceShowOSD(Song, bool)), SLOT(ForceShowOSD(Song, bool))); + connect(app_->player(), SIGNAL(SendNowPlaying()), SLOT(SendNowPlaying())); + connect(app_->playlist_manager(), SIGNAL(CurrentSongChanged(Song)), SLOT(SongChanged(Song))); connect(app_->playlist_manager(), SIGNAL(CurrentSongChanged(Song)), app_->player(), SLOT(CurrentMetadataChanged(Song))); connect(app_->playlist_manager(), SIGNAL(EditingFinished(QModelIndex)), SLOT(PlaylistEditFinished(QModelIndex))); @@ -854,15 +856,13 @@ MainWindow::MainWindow(Application *app, SystemTrayIcon *tray_icon, OSD *osd, co if (!options.contains_play_options()) { LoadPlaybackStatus(); } + if (app_->scrobbler()->IsEnabled() && !app_->scrobbler()->IsOffline()) app_->scrobbler()->Submit(); RefreshStyleSheet(); qLog(Debug) << "Started" << QThread::currentThread(); initialised_ = true; - app_->scrobbler()->ConnectError(); - if (app_->scrobbler()->IsEnabled() && !app_->scrobbler()->IsOffline()) app_->scrobbler()->Submit(); - } MainWindow::~MainWindow() { @@ -1101,10 +1101,18 @@ void MainWindow::MediaPlaying() { track_position_timer_->start(); track_slider_timer_->start(); UpdateTrackPosition(); + SendNowPlaying(); + +} + +void MainWindow::SendNowPlaying() { + + PlaylistItemPtr item(app_->player()->GetCurrentItem()); + if (!item) return; // Send now playing to scrobble services Playlist *playlist = app_->playlist_manager()->active(); - if (app_->scrobbler()->IsEnabled() && playlist && !playlist->nowplaying() && item->Metadata().is_metadata_good() && item->Metadata().length_nanosec() > 0) { + if (app_->scrobbler()->IsEnabled() && playlist && !playlist->nowplaying() && item->Metadata().is_metadata_good()) { app_->scrobbler()->UpdateNowPlaying(item->Metadata()); playlist->set_nowplaying(true); ui_->action_love->setEnabled(true); @@ -2039,7 +2047,7 @@ void MainWindow::CommandlineOptionsReceived(const CommandlineOptions &options) { if (!options.urls().empty()) { #ifdef HAVE_TIDAL - for (const QUrl url : options.urls()) { + for (const QUrl &url : options.urls()) { if (url.scheme() == "tidal" && url.host() == "login") { emit AuthorisationUrlReceived(url); return; @@ -2671,7 +2679,7 @@ void MainWindow::LoveButtonVisibilityChanged(const bool value) { void MainWindow::SetToggleScrobblingIcon(const bool value) { if (value) { - if (app_->playlist_manager()->active()->scrobbled()) + if (app_->playlist_manager()->active() && app_->playlist_manager()->active()->scrobbled()) ui_->action_toggle_scrobbling->setIcon(IconLoader::Load("scrobble", 22)); else ui_->action_toggle_scrobbling->setIcon(IconLoader::Load("scrobble", 22)); // TODO: Create a faint version of the icon diff --git a/src/core/mainwindow.h b/src/core/mainwindow.h index 6e87d9300..d44a23a64 100644 --- a/src/core/mainwindow.h +++ b/src/core/mainwindow.h @@ -258,6 +258,7 @@ class MainWindow : public QMainWindow, public PlatformInterface { void ScrobblingEnabledChanged(const bool value); void ScrobbleButtonVisibilityChanged(const bool value); void LoveButtonVisibilityChanged(const bool value); + void SendNowPlaying(); void Love(); void ExitFinished(); diff --git a/src/core/player.cpp b/src/core/player.cpp index d7d49dc84..89e2fde1b 100644 --- a/src/core/player.cpp +++ b/src/core/player.cpp @@ -622,9 +622,8 @@ void Player::CurrentMetadataChanged(const Song &metadata) { if (app_->scrobbler()->IsEnabled() && engine_->state() == Engine::Playing) { Playlist *playlist = app_->playlist_manager()->active(); current_item_ = playlist->current_item(); - if (playlist && current_item_ && !playlist->nowplaying() && current_item_->Metadata() == metadata && current_item_->Metadata().length_nanosec() > 0) { - app_->scrobbler()->UpdateNowPlaying(metadata); - playlist->set_nowplaying(true); + if (playlist && current_item_ && !playlist->nowplaying() && current_item_->Metadata() == metadata && current_item_->Metadata().is_metadata_good()) { + emit SendNowPlaying(); } } diff --git a/src/core/player.h b/src/core/player.h index d8c2df4e9..02d59ec8d 100644 --- a/src/core/player.h +++ b/src/core/player.h @@ -121,6 +121,7 @@ class PlayerInterface : public QObject { // The toggle parameter is true when user requests to toggle visibility for Pretty OSD void ForceShowOSD(Song, bool toggle); + void SendNowPlaying(); void Authenticated(); }; diff --git a/src/core/song.cpp b/src/core/song.cpp index 025f340b3..c91061e36 100644 --- a/src/core/song.cpp +++ b/src/core/song.cpp @@ -348,7 +348,7 @@ const QString &Song::cue_path() const { return d->cue_path_; } bool Song::has_cue() const { return !d->cue_path_.isEmpty(); } bool Song::is_collection_song() const { return d->source_ == Source_Collection; } -bool Song::is_metadata_good() const { return !d->title_.isEmpty() && !d->artist_.isEmpty() && !d->url_.isEmpty() && d->end_ > 0; } +bool Song::is_metadata_good() const { return !d->url_.isEmpty() && !d->artist_.isEmpty() && !d->title_.isEmpty(); } bool Song::is_stream() const { return d->source_ == Source_Stream || d->source_ == Source_Tidal || d->source_ == Source_Subsonic || d->source_ == Source_Qobuz; } bool Song::is_cdda() const { return d->source_ == Source_CDDA; } bool Song::is_compilation() const { return (d->compilation_ || d->compilation_detected_ || d->compilation_on_) && !d->compilation_off_; } diff --git a/src/playlist/playlist.cpp b/src/playlist/playlist.cpp index df87fbd5b..e6581e537 100644 --- a/src/playlist/playlist.cpp +++ b/src/playlist/playlist.cpp @@ -611,7 +611,6 @@ void Playlist::set_current_row(int i, bool is_stopping) { if (current_item_index_ == old_current_item_index) { UpdateScrobblePoint(); - nowplaying_ = false; return; } @@ -649,7 +648,6 @@ void Playlist::set_current_row(int i, bool is_stopping) { } UpdateScrobblePoint(); - nowplaying_ = false; } @@ -1503,7 +1501,7 @@ void Playlist::SetStreamMetadata(const QUrl &url, const Song &song, const bool m //qLog(Debug) << "Setting temporary metadata for" << url; - bool length_changed = song.length_nanosec() != current_item_metadata().length_nanosec(); + bool update_scrobble_point = song.length_nanosec() != current_item_metadata().length_nanosec(); current_item()->SetTemporaryMetadata(song); @@ -1518,10 +1516,11 @@ void Playlist::SetStreamMetadata(const QUrl &url, const Song &song, const bool m } } else { + update_scrobble_point = true; InformOfCurrentSongChange(); } - if (length_changed) UpdateScrobblePoint(); + if (update_scrobble_point) UpdateScrobblePoint(); } @@ -2022,6 +2021,7 @@ void Playlist::UpdateScrobblePoint(const qint64 seek_point_nanosec) { } } + nowplaying_ = false; scrobbled_ = false; } diff --git a/src/scrobbler/audioscrobbler.cpp b/src/scrobbler/audioscrobbler.cpp index b9d5de01f..affb07c79 100644 --- a/src/scrobbler/audioscrobbler.cpp +++ b/src/scrobbler/audioscrobbler.cpp @@ -55,6 +55,10 @@ AudioScrobbler::AudioScrobbler(Application *app, QObject *parent) : ReloadSettings(); + for (ScrobblerService *service : scrobbler_services_->List()) { + connect(service, SIGNAL(ErrorMessage(QString)), SLOT(ErrorReceived(QString))); + } + } AudioScrobbler::~AudioScrobbler() {} @@ -116,53 +120,61 @@ void AudioScrobbler::ShowConfig() { } void AudioScrobbler::UpdateNowPlaying(const Song &song) { + qLog(Debug) << "Sending now playing for song" << song.artist() << song.album() << song.title(); + for (ScrobblerService *service : scrobbler_services_->List()) { if (!service->IsEnabled()) continue; service->UpdateNowPlaying(song); } + } void AudioScrobbler::ClearPlaying() { + for (ScrobblerService *service : scrobbler_services_->List()) { if (!service->IsEnabled()) continue; service->ClearPlaying(); } + } void AudioScrobbler::Scrobble(const Song &song, const int scrobble_point) { + qLog(Debug) << "Scrobbling song" << song.artist() << song.album() << song.title() << "at" << scrobble_point; + for (ScrobblerService *service : scrobbler_services_->List()) { if (!service->IsEnabled()) continue; service->Scrobble(song); } + } void AudioScrobbler::Love() { + for (ScrobblerService *service : scrobbler_services_->List()) { if (!service->IsEnabled() || !service->IsAuthenticated()) continue; service->Love(); } + } void AudioScrobbler::Submit() { + for (ScrobblerService *service : scrobbler_services_->List()) { if (!service->IsEnabled() || !service->IsAuthenticated() || service->IsSubmitted()) continue; service->DoSubmit(); } + } void AudioScrobbler::WriteCache() { + for (ScrobblerService *service : scrobbler_services_->List()) { if (!service->IsEnabled()) continue; service->WriteCache(); } -} -void AudioScrobbler::ConnectError() { - for (ScrobblerService *service : scrobbler_services_->List()) { - connect(service, SIGNAL(ErrorMessage(QString)), SLOT(ErrorReceived(QString))); - } } void AudioScrobbler::ErrorReceived(QString error) { diff --git a/src/scrobbler/audioscrobbler.h b/src/scrobbler/audioscrobbler.h index cbe09ba80..f7db04c1c 100644 --- a/src/scrobbler/audioscrobbler.h +++ b/src/scrobbler/audioscrobbler.h @@ -51,7 +51,6 @@ class AudioScrobbler : public QObject { void ClearPlaying(); void Scrobble(const Song &song, const int scrobble_point); void ShowConfig(); - void ConnectError(); ScrobblerService *ServiceByName(const QString &name) const { return scrobbler_services_->ServiceByName(name); } diff --git a/src/scrobbler/listenbrainzscrobbler.cpp b/src/scrobbler/listenbrainzscrobbler.cpp index be2514c4d..9d27c0416 100644 --- a/src/scrobbler/listenbrainzscrobbler.cpp +++ b/src/scrobbler/listenbrainzscrobbler.cpp @@ -70,6 +70,7 @@ ListenBrainzScrobbler::ListenBrainzScrobbler(Application *app, QObject *parent) enabled_(false), expires_in_(-1), submitted_(false), + scrobbled_(false), timestamp_(0) { ReloadSettings(); @@ -345,7 +346,10 @@ QByteArray ListenBrainzScrobbler::GetReplyData(QNetworkReply *reply) { void ListenBrainzScrobbler::UpdateNowPlaying(const Song &song) { + CheckScrobblePrevSong(); + song_playing_ = song; + scrobbled_ = false; timestamp_ = QDateTime::currentDateTime().toTime_t(); if (!song.is_metadata_good() || !IsAuthenticated() || app_->scrobbler()->IsOffline()) return; @@ -418,13 +422,20 @@ void ListenBrainzScrobbler::UpdateNowPlayingRequestFinished(QNetworkReply *reply } void ListenBrainzScrobbler::ClearPlaying() { + + CheckScrobblePrevSong(); song_playing_ = Song(); + scrobbled_ = false; + timestamp_ = 0; + } void ListenBrainzScrobbler::Scrobble(const Song &song) { if (song.id() != song_playing_.id() || song.url() != song_playing_.url() || !song.is_metadata_good()) return; + scrobbled_ = true; + cache_->Add(song, timestamp_); if (app_->scrobbler()->IsOffline()) return; @@ -552,3 +563,12 @@ void ListenBrainzScrobbler::Error(const QString &error, const QVariant &debug) { } +void ListenBrainzScrobbler::CheckScrobblePrevSong() { + + quint64 duration = QDateTime::currentDateTime().toTime_t() - timestamp_; + + if (!scrobbled_ && song_playing_.is_metadata_good() && song_playing_.source() == Song::Source_Stream && duration > 30) { + Scrobble(song_playing_); + } + +} diff --git a/src/scrobbler/listenbrainzscrobbler.h b/src/scrobbler/listenbrainzscrobbler.h index f69e6966e..0a5a7dfab 100644 --- a/src/scrobbler/listenbrainzscrobbler.h +++ b/src/scrobbler/listenbrainzscrobbler.h @@ -88,6 +88,7 @@ class ListenBrainzScrobbler : public ScrobblerService { void AuthError(const QString &error); void Error(const QString &error, const QVariant &debug = QVariant()); void DoSubmit(); + void CheckScrobblePrevSong(); static const char *kAuthUrl; static const char *kAuthTokenUrl; @@ -110,6 +111,7 @@ class ListenBrainzScrobbler : public ScrobblerService { QString refresh_token_; bool submitted_; Song song_playing_; + bool scrobbled_; quint64 timestamp_; }; diff --git a/src/scrobbler/scrobblingapi20.cpp b/src/scrobbler/scrobblingapi20.cpp index 5ba0aec8f..4db37aae1 100644 --- a/src/scrobbler/scrobblingapi20.cpp +++ b/src/scrobbler/scrobblingapi20.cpp @@ -77,6 +77,7 @@ ScrobblingAPI20::ScrobblingAPI20(const QString &name, const QString &settings_gr enabled_(false), subscriber_(false), submitted_(false), + scrobbled_(false), timestamp_(0) {} ScrobblingAPI20::~ScrobblingAPI20() {} @@ -422,8 +423,11 @@ QByteArray ScrobblingAPI20::GetReplyData(QNetworkReply *reply) { void ScrobblingAPI20::UpdateNowPlaying(const Song &song) { + CheckScrobblePrevSong(); + song_playing_ = song; timestamp_ = QDateTime::currentDateTime().toTime_t(); + scrobbled_ = false; if (!IsAuthenticated() || !song.is_metadata_good() || app_->scrobbler()->IsOffline()) return; @@ -477,13 +481,21 @@ void ScrobblingAPI20::UpdateNowPlayingRequestFinished(QNetworkReply *reply) { } void ScrobblingAPI20::ClearPlaying() { + + CheckScrobblePrevSong(); + song_playing_ = Song(); + scrobbled_ = false; + timestamp_ = 0; + } void ScrobblingAPI20::Scrobble(const Song &song) { if (song.id() != song_playing_.id() || song.url() != song_playing_.url() || !song.is_metadata_good()) return; + scrobbled_ = true; + cache()->Add(song, timestamp_); if (app_->scrobbler()->IsOffline()) return; @@ -595,15 +607,15 @@ void ScrobblingAPI20::ScrobbleRequestFinished(QNetworkReply *reply, QListFlush(list); - QJsonValue json_scrobbles = json_obj["scrobbles"]; - if (!json_scrobbles.isObject()) { + QJsonValue value_scrobbles = json_obj["scrobbles"]; + if (!value_scrobbles.isObject()) { Error("Json scrobbles is not an object.", json_obj); DoSubmit(); return; } - json_obj = json_scrobbles.toObject(); + json_obj = value_scrobbles.toObject(); if (json_obj.isEmpty()) { - Error("Json scrobbles object is empty.", json_scrobbles); + Error("Json scrobbles object is empty.", value_scrobbles); DoSubmit(); return; } @@ -775,14 +787,14 @@ void ScrobblingAPI20::SingleScrobbleRequestFinished(QNetworkReply *reply, quint6 cache()->Remove(timestamp); item = nullptr; - QJsonValue json_scrobbles = json_obj["scrobbles"]; - if (!json_scrobbles.isObject()) { + QJsonValue value_scrobbles = json_obj["scrobbles"]; + if (!value_scrobbles.isObject()) { Error("Json scrobbles is not an object.", json_obj); return; } - json_obj = json_scrobbles.toObject(); + json_obj = value_scrobbles.toObject(); if (json_obj.isEmpty()) { - Error("Json scrobbles object is empty.", json_scrobbles); + Error("Json scrobbles object is empty.", value_scrobbles); return; } if (!json_obj.contains("@attr") || !json_obj.contains("scrobble")) { @@ -980,3 +992,13 @@ QString ScrobblingAPI20::ErrorString(const ScrobbleErrorCode error) const { } } + +void ScrobblingAPI20::CheckScrobblePrevSong() { + + quint64 time = QDateTime::currentDateTime().toTime_t() - timestamp_; + + if (!scrobbled_ && song_playing_.is_metadata_good() && song_playing_.source() == Song::Source_Stream && time > 30) { + Scrobble(song_playing_); + } + +} diff --git a/src/scrobbler/scrobblingapi20.h b/src/scrobbler/scrobblingapi20.h index 9f3777657..97924b2cb 100644 --- a/src/scrobbler/scrobblingapi20.h +++ b/src/scrobbler/scrobblingapi20.h @@ -133,6 +133,7 @@ class ScrobblingAPI20 : public ScrobblerService { void Error(const QString &error, const QVariant &debug = QVariant()); QString ErrorString(const ScrobbleErrorCode error) const; void DoSubmit(); + void CheckScrobblePrevSong(); QString name_; QString settings_group_; @@ -153,6 +154,7 @@ class ScrobblingAPI20 : public ScrobblerService { bool submitted_; Song song_playing_; + bool scrobbled_; quint64 timestamp_; };