File indexing completed on 2024-05-12 16:21:18
0001 // SPDX-FileCopyrightText: 2021 Jonah BrĂ¼chert <jbb@kaidan.im> 0002 // 0003 // SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 0004 0005 #include "userplaylistmodel.h" 0006 0007 #include <asyncytmusic.h> 0008 #include <algorithm> 0009 #include <random> 0010 0011 #include <QRandomGenerator> 0012 0013 #include <QStringBuilder> 0014 0015 #include <iostream> 0016 0017 #include "albummodel.h" 0018 #include "localplaylistmodel.h" 0019 #include "playlistutils.h" 0020 #include "playlistmodel.h" 0021 0022 namespace ranges = std::ranges; 0023 0024 UserPlaylistModel::UserPlaylistModel(QObject *parent) 0025 : AbstractYTMusicModel(parent) 0026 { 0027 auto handleResult = [=, this](watch::Playlist &&playlist) { 0028 setLoading(false); 0029 0030 beginResetModel(); 0031 m_playlist = std::move(playlist); 0032 endResetModel(); 0033 setCurrentVideoId({}); 0034 if (m_shuffle) { 0035 shufflePlaylist(); 0036 0037 // reset shuffle 0038 setShuffle(false); 0039 } 0040 if (!m_playlist.tracks.empty()) { 0041 setCurrentVideoId(QString::fromStdString(m_playlist.tracks.front().video_id)); 0042 } 0043 }; 0044 connect(this, &UserPlaylistModel::initialVideoIdChanged, this, [=, this] { 0045 if (m_initialVideoId.isEmpty()) { 0046 return; 0047 } 0048 0049 setLoading(true); 0050 auto future = YTMusicThread::instance()->fetchWatchPlaylist(m_initialVideoId); 0051 QCoro::connect(std::move(future), this, handleResult); 0052 }); 0053 connect(this, &UserPlaylistModel::playlistIdChanged, this, [=, this] { 0054 if (m_playlistId.isEmpty()) { 0055 return; 0056 } 0057 0058 setLoading(true); 0059 auto future = YTMusicThread::instance()->fetchWatchPlaylist(std::nullopt, m_playlistId); 0060 QCoro::connect(std::move(future), this, handleResult);; 0061 }); 0062 connect(&YTMusicThread::instance().get(), &AsyncYTMusic::errorOccurred, this, [this] { 0063 setLoading(false); 0064 }); 0065 connect(this, &UserPlaylistModel::currentVideoIdChanged, this, [this]() { 0066 Q_EMIT currentIndexChanged(); 0067 // Clear lyrics, so we won't display old ones if the next song doesn't have any. 0068 m_lyrics = {}; 0069 Q_EMIT lyricsChanged(); 0070 0071 if (!m_currentVideoId.isEmpty()) { 0072 fetchLyrics(m_currentVideoId); 0073 } 0074 }); 0075 } 0076 0077 int UserPlaylistModel::rowCount(const QModelIndex &parent) const 0078 { 0079 return parent.isValid() ? 0 : int(m_playlist.tracks.size()); 0080 } 0081 0082 QVariant UserPlaylistModel::data(const QModelIndex &index, int role) const 0083 { 0084 switch (role) { 0085 case Title: 0086 return QString::fromStdString(m_playlist.tracks[index.row()].title); 0087 case VideoId: 0088 return QString::fromStdString(m_playlist.tracks[index.row()].video_id); 0089 case Artists: 0090 return PlaylistUtils::artistsToString(m_playlist.tracks[index.row()].artists); 0091 case Album: 0092 return QString::fromStdString(m_playlist.tracks[index.row()].album.value_or(meta::Album()).name); 0093 case IsCurrent: 0094 return m_playlist.tracks[index.row()].video_id == m_currentVideoId.toStdString(); 0095 } 0096 0097 Q_UNREACHABLE(); 0098 0099 return {}; 0100 } 0101 0102 QHash<int, QByteArray> UserPlaylistModel::roleNames() const 0103 { 0104 return { 0105 {Title, "title"}, 0106 {VideoId, "videoId"}, 0107 {Artists, "artists"}, 0108 {Album, "album"}, 0109 {IsCurrent, "isCurrent"}, 0110 }; 0111 } 0112 0113 bool UserPlaylistModel::moveRow(int sourceRow,int destinationRow) 0114 { 0115 if(beginMoveRows(QModelIndex(), sourceRow, sourceRow, QModelIndex(), destinationRow)) { 0116 m_playlist.tracks.insert(m_playlist.tracks.begin()+destinationRow, 1, m_playlist.tracks[sourceRow]); 0117 if(sourceRow < destinationRow) { 0118 m_playlist.tracks.erase(m_playlist.tracks.begin()+sourceRow); 0119 } 0120 else { 0121 m_playlist.tracks.erase(m_playlist.tracks.begin()+sourceRow+1); 0122 } 0123 endMoveRows(); 0124 0125 Q_EMIT currentIndexChanged(); 0126 Q_EMIT canSkipChanged(); 0127 Q_EMIT canSkipBackChanged(); 0128 return true; 0129 } 0130 return false; 0131 } 0132 0133 QString UserPlaylistModel::initialVideoId() const 0134 { 0135 return m_initialVideoId; 0136 } 0137 0138 void UserPlaylistModel::setInitialVideoId(const QString &videoId) 0139 { 0140 m_initialVideoId = videoId; 0141 Q_EMIT initialVideoIdChanged(); 0142 } 0143 0144 QString UserPlaylistModel::nextVideoId() const 0145 { 0146 auto currentTrack = std::find_if(m_playlist.tracks.begin(), m_playlist.tracks.end(), 0147 [this](const watch::Playlist::Track &track) { 0148 return track.video_id == m_currentVideoId.toStdString(); 0149 }); 0150 0151 if (currentTrack == m_playlist.tracks.end() || currentTrack + 1 == m_playlist.tracks.end()) { 0152 return {}; 0153 } 0154 0155 return QString::fromStdString((currentTrack + 1)->video_id); 0156 } 0157 0158 QString UserPlaylistModel::previousVideoId() const 0159 { 0160 auto currentTrack = std::find_if(m_playlist.tracks.begin(), m_playlist.tracks.end(), 0161 [this](const watch::Playlist::Track &track) { 0162 return track.video_id == m_currentVideoId.toStdString(); 0163 }); 0164 0165 if (currentTrack == m_playlist.tracks.end() || currentTrack - 1 == m_playlist.tracks.end()) { 0166 return {}; 0167 } 0168 0169 return QString::fromStdString((currentTrack - 1)->video_id); 0170 } 0171 0172 QString UserPlaylistModel::currentVideoId() const 0173 { 0174 return m_currentVideoId; 0175 } 0176 0177 void UserPlaylistModel::setCurrentVideoId(const QString &videoId) 0178 { 0179 const auto old = m_currentVideoId; 0180 m_currentVideoId = videoId; 0181 emitCurrentVideoChanged(old); 0182 Q_EMIT currentVideoIdChanged(); 0183 Q_EMIT canSkipChanged(); 0184 Q_EMIT canSkipBackChanged(); 0185 0186 } 0187 0188 int UserPlaylistModel::currentIndex() const 0189 { 0190 auto currentTrack = ranges::find_if(m_playlist.tracks, 0191 [this](const watch::Playlist::Track &track) { 0192 return track.video_id == m_currentVideoId.toStdString(); 0193 }); 0194 0195 return std::distance(m_playlist.tracks.begin(), currentTrack); 0196 } 0197 0198 bool UserPlaylistModel::canSkip() const 0199 { 0200 const auto currentTrackIt = ranges::find_if(m_playlist.tracks, 0201 [this](const watch::Playlist::Track &track) { 0202 return track.video_id == m_currentVideoId.toStdString(); 0203 }); 0204 0205 return currentTrackIt != m_playlist.tracks.end() - 1 0206 && currentTrackIt != m_playlist.tracks.end() 0207 && !m_playlist.tracks.empty(); 0208 } 0209 0210 bool UserPlaylistModel::canSkipBack() const 0211 { 0212 const auto currentTrackIt = std::ranges::find_if(m_playlist.tracks, [this](const watch::Playlist::Track &track) { 0213 return track.video_id == m_currentVideoId.toStdString(); 0214 }); 0215 0216 return currentTrackIt != m_playlist.tracks.begin() 0217 && !m_playlist.tracks.empty(); 0218 } 0219 0220 void UserPlaylistModel::next() 0221 { 0222 setCurrentVideoId(nextVideoId()); 0223 Q_EMIT currentVideoIdChanged(); 0224 Q_EMIT canSkipChanged(); 0225 Q_EMIT canSkipBackChanged(); 0226 0227 } 0228 0229 void UserPlaylistModel::previous() 0230 { 0231 setCurrentVideoId(previousVideoId()); 0232 Q_EMIT currentVideoIdChanged(); 0233 Q_EMIT canSkipChanged(); 0234 Q_EMIT canSkipBackChanged(); 0235 0236 } 0237 0238 void UserPlaylistModel::skipTo(const QString &videoId) 0239 { 0240 const auto old = m_currentVideoId; 0241 m_currentVideoId = videoId; 0242 emitCurrentVideoChanged(old); 0243 Q_EMIT currentVideoIdChanged(); 0244 Q_EMIT canSkipChanged(); 0245 Q_EMIT canSkipBackChanged(); 0246 0247 } 0248 0249 void UserPlaylistModel::playNext(const QString &videoId, const QString &title, const std::vector<meta::Artist> &artists) 0250 { 0251 const auto currentIt = ranges::find_if(m_playlist.tracks, 0252 [this](const watch::Playlist::Track &track) { 0253 return track.video_id == m_currentVideoId.toStdString(); 0254 }); 0255 0256 watch::Playlist::Track track; 0257 track.video_id = videoId.toStdString(); 0258 track.title = title.toStdString(); 0259 track.artists = artists; 0260 0261 if (currentIt == m_playlist.tracks.end() || m_playlist.tracks.empty()) { 0262 beginInsertRows({}, 0, 0); 0263 m_playlist.tracks.push_back(std::move(track)); 0264 endInsertRows(); 0265 setCurrentVideoId(videoId); 0266 Q_EMIT canSkipChanged(); 0267 Q_EMIT canSkipBackChanged(); 0268 0269 return; 0270 } 0271 0272 if (currentIt == m_playlist.tracks.end() - 1) { 0273 int index = std::distance(m_playlist.tracks.begin(), currentIt + 1); 0274 beginInsertRows({}, index, index); 0275 m_playlist.tracks.push_back(std::move(track)); 0276 endInsertRows(); 0277 Q_EMIT canSkipChanged(); 0278 Q_EMIT canSkipBackChanged(); 0279 0280 return; 0281 } 0282 0283 int index = std::distance(m_playlist.tracks.begin(), currentIt + 1); 0284 beginInsertRows({}, index, index); 0285 m_playlist.tracks.insert(currentIt + 1, std::move(track)); 0286 endInsertRows(); 0287 Q_EMIT canSkipChanged(); 0288 Q_EMIT canSkipBackChanged(); 0289 0290 } 0291 0292 void UserPlaylistModel::append(const QString &videoId, const QString &title, const std::vector<meta::Artist> &artists) 0293 { 0294 watch::Playlist::Track track; 0295 track.video_id = videoId.toStdString(); 0296 track.title = title.toStdString(); 0297 track.artists = artists; 0298 0299 beginInsertRows({}, m_playlist.tracks.size(), m_playlist.tracks.size()); 0300 m_playlist.tracks.push_back(std::move(track)); 0301 endInsertRows(); 0302 0303 if (m_playlist.tracks.size() == 1) { 0304 setCurrentVideoId(videoId); 0305 } 0306 0307 Q_EMIT canSkipChanged(); 0308 } 0309 0310 void UserPlaylistModel::clear() 0311 { 0312 beginResetModel(); 0313 m_playlist.tracks.clear(); 0314 endResetModel(); 0315 0316 setCurrentVideoId({}); 0317 Q_EMIT canSkipChanged(); 0318 Q_EMIT canSkipBackChanged(); 0319 } 0320 0321 void UserPlaylistModel::clearExceptCurrent() 0322 { 0323 int index = currentIndex(); 0324 Q_ASSERT(checkIndex(createIndex(index, 0), CheckIndexOption::IndexIsValid | CheckIndexOption::DoNotUseParent)); 0325 if(m_playlist.tracks.empty()) {return;} 0326 if((unsigned) index < m_playlist.tracks.size() - 1) { 0327 beginRemoveRows({}, index + 1, m_playlist.tracks.size() - 1); 0328 m_playlist.tracks.erase(m_playlist.tracks.begin() + index + 1, m_playlist.tracks.end()); 0329 endRemoveRows(); 0330 } 0331 0332 if(index > 0) { 0333 beginRemoveRows({}, 0, index - 1); 0334 m_playlist.tracks.erase(m_playlist.tracks.begin(), m_playlist.tracks.begin() + index); 0335 endRemoveRows(); 0336 } 0337 0338 Q_EMIT canSkipChanged(); 0339 Q_EMIT canSkipBackChanged(); 0340 } 0341 0342 0343 void UserPlaylistModel::remove(const QString &videoId) 0344 { 0345 if (m_currentVideoId == videoId) { 0346 setCurrentVideoId(nextVideoId()); 0347 } 0348 0349 const auto trackIt = ranges::find_if(m_playlist.tracks, [&](const watch::Playlist::Track &track) { 0350 return track.video_id == videoId.toStdString(); 0351 }); 0352 int index = std::distance(m_playlist.tracks.begin(), trackIt); 0353 beginRemoveRows({}, index, index); 0354 m_playlist.tracks.erase(trackIt); 0355 endRemoveRows(); 0356 0357 Q_EMIT canSkipChanged(); 0358 Q_EMIT canSkipBackChanged(); 0359 } 0360 0361 void UserPlaylistModel::shufflePlaylist() 0362 { 0363 // Only shuffle playlist after current track 0364 if (!m_currentVideoId.isEmpty()) { 0365 const auto currentIt = ranges::find_if(m_playlist.tracks, 0366 [this](const watch::Playlist::Track &track) { 0367 return track.video_id == m_currentVideoId.toStdString(); 0368 }); 0369 0370 std::shuffle(currentIt + 1, m_playlist.tracks.end(), *QRandomGenerator::global()); 0371 } else { 0372 ranges::shuffle(m_playlist.tracks, *QRandomGenerator::global()); 0373 } 0374 Q_EMIT dataChanged(index(0), index(m_playlist.tracks.size() - 1), {}); 0375 } 0376 0377 void UserPlaylistModel::appendPlaylist(PlaylistModel *playlistModel) 0378 { 0379 for (const auto &track : playlistModel->playlist().tracks) { 0380 if (track.video_id) { 0381 append(QString::fromStdString(*track.video_id), QString::fromStdString(track.title), track.artists); 0382 } 0383 } 0384 } 0385 0386 void UserPlaylistModel::appendAlbum(AlbumModel *albumModel) 0387 { 0388 for (const auto &track : albumModel->album().tracks) { 0389 if (track.video_id) { 0390 append(QString::fromStdString(*track.video_id), QString::fromStdString(track.title), track.artists); 0391 } 0392 } 0393 } 0394 0395 void UserPlaylistModel::playFavourites(FavouritesModel *favouriteModel, bool shuffled) 0396 { 0397 clear(); 0398 appendFavourites(favouriteModel, shuffled); 0399 0400 } 0401 0402 void UserPlaylistModel::appendFavourites(FavouritesModel *favouriteModel, bool shuffled) 0403 { 0404 std::vector<Song> favourites(favouriteModel->getFavouriteSongs()); 0405 if(shuffled) { 0406 ranges::shuffle(favourites, *QRandomGenerator::global()); 0407 } 0408 ranges::for_each(favourites, [this](const Song &song) { 0409 meta::Artist artist; 0410 artist.name = song.artist.toStdString(); 0411 append(song.videoId, song.title, std::vector<meta::Artist>({artist})); 0412 }); 0413 } 0414 0415 void UserPlaylistModel::playPlaybackHistory(PlaybackHistoryModel *playbackHistory, bool shuffled) 0416 { 0417 clear(); 0418 appendPlaybackHistory(playbackHistory, shuffled); 0419 0420 } 0421 0422 void UserPlaylistModel::appendPlaybackHistory(PlaybackHistoryModel *playbackHistory, bool shuffled) 0423 { 0424 std::vector<PlayedSong> playedSongs(playbackHistory->getPlayedSong()); 0425 if(shuffled) { 0426 std::shuffle(playedSongs.begin(), playedSongs.end(), *QRandomGenerator::global()); 0427 } 0428 ranges::for_each(playedSongs, [this](const PlayedSong &song) { 0429 meta::Artist artist; 0430 artist.name = song.artist.toStdString(); 0431 append(song.videoId, song.title, std::vector<meta::Artist>({artist})); 0432 }); 0433 } 0434 0435 void UserPlaylistModel::playLocalPlaylist(LocalPlaylistModel *playlistModel, bool shuffled) 0436 { 0437 clear(); 0438 appendLocalPlaylist(playlistModel, shuffled); 0439 } 0440 0441 void UserPlaylistModel::appendLocalPlaylist(LocalPlaylistModel *playlistModel, bool shuffled) 0442 { 0443 std::vector<PlaylistEntry> entries = playlistModel->entries(); 0444 if (shuffled) { 0445 ranges::shuffle(entries, *QRandomGenerator::global()); 0446 } 0447 for (const auto &entry : entries) { 0448 meta::Artist artist; 0449 artist.name = entry.artists.toStdString(); 0450 append(entry.videoId, entry.title, std::vector<meta::Artist>({artist})); 0451 } 0452 } 0453 0454 void UserPlaylistModel::emitCurrentVideoChanged(const QString &oldVideoId) 0455 { 0456 0457 const auto oldVideoIt = ranges::find_if(m_playlist.tracks, 0458 [=](const watch::Playlist::Track &track) { 0459 return track.video_id == oldVideoId.toStdString(); 0460 }); 0461 const auto currentVideoIt = ranges::find_if(m_playlist.tracks, 0462 [this](const watch::Playlist::Track &track) { 0463 return track.video_id == m_currentVideoId.toStdString(); 0464 }); 0465 0466 int oldIndex = std::distance(m_playlist.tracks.begin(), oldVideoIt); 0467 int newIndex = std::distance(m_playlist.tracks.begin(), currentVideoIt); 0468 0469 Q_EMIT dataChanged(index(oldIndex), index(oldIndex), {IsCurrent}); 0470 Q_EMIT dataChanged(index(newIndex), index(newIndex), {IsCurrent}); 0471 } 0472 0473 void UserPlaylistModel::fetchLyrics(const QString &videoId) 0474 { 0475 auto future = YTMusicThread::instance()->fetchWatchPlaylist(videoId); 0476 QCoro::connect(std::move(future), this, [=, this](const auto &playlist) { 0477 if (playlist.lyrics) { 0478 QCoro::connect(YTMusicThread::instance()->fetchLyrics(QString::fromStdString(*playlist.lyrics)), this, [=, this](const auto &lyrics) { 0479 m_lyrics = lyrics; 0480 Q_EMIT lyricsChanged(); 0481 }); 0482 } else { 0483 Q_EMIT noLyrics(); 0484 } 0485 }); 0486 } 0487 0488 bool UserPlaylistModel::shuffle() const 0489 { 0490 return m_shuffle; 0491 } 0492 0493 QString UserPlaylistModel::lyrics() const { 0494 return QString::fromStdString(m_lyrics.lyrics); 0495 } 0496 0497 void UserPlaylistModel::setShuffle(bool shuffle) 0498 { 0499 m_shuffle = shuffle; 0500 Q_EMIT shuffleChanged(); 0501 } 0502 0503 QString UserPlaylistModel::playlistId() const 0504 { 0505 return m_playlistId; 0506 } 0507 0508 void UserPlaylistModel::setPlaylistId(const QString &playlistId) 0509 { 0510 m_playlistId = playlistId; 0511 Q_EMIT playlistIdChanged(); 0512 } 0513 0514 QUrl UserPlaylistModel::webUrl() const 0515 { 0516 return QUrl(YTMUSIC_WEB_BASE_URL % "watch?v=" % m_currentVideoId); 0517 } 0518