File indexing completed on 2025-02-23 04:35:15
0001 // SPDX-FileCopyrightText: 2023 Joshua Goins <josh@redstrate.com> 0002 // SPDX-License-Identifier: GPL-3.0-or-later 0003 0004 #include "videoqueue.h" 0005 0006 #include "plasmatube.h" 0007 0008 VideoQueue::VideoQueue(QObject *parent) 0009 : QAbstractListModel(parent) 0010 { 0011 } 0012 0013 void VideoQueue::replace(const QStringList &videoIds) 0014 { 0015 beginResetModel(); 0016 m_videoIds = videoIds; 0017 m_videoInfo.clear(); 0018 m_videoInfo.resize(videoIds.size()); 0019 endResetModel(); 0020 Q_EMIT queueChanged(); 0021 0022 requestMissingVideoInformation(); 0023 0024 setCurrentIndex(0); 0025 } 0026 0027 void VideoQueue::queueNext(const QString &videoId) 0028 { 0029 beginInsertRows({}, m_videoIds.size(), m_videoIds.size()); 0030 m_videoIds.push_back(videoId); 0031 m_videoInfo.push_back(std::nullopt); 0032 endInsertRows(); 0033 Q_EMIT queueChanged(); 0034 0035 requestMissingVideoInformation(); 0036 } 0037 0038 void VideoQueue::clear() 0039 { 0040 beginResetModel(); 0041 m_videoIds.clear(); 0042 m_videoInfo.clear(); 0043 endResetModel(); 0044 Q_EMIT queueChanged(); 0045 0046 setCurrentIndex(0); 0047 } 0048 0049 void VideoQueue::playInQueue(const int videoIndex) 0050 { 0051 // TODO: protect against invalid indexes 0052 setCurrentIndex(videoIndex); 0053 } 0054 0055 void VideoQueue::loadPlaylist(const QString &playlistId) 0056 { 0057 auto playlistFuture = PlasmaTube::instance().sourceManager()->selectedSource()->api()->requestPlaylist(playlistId); 0058 0059 auto playlistFutureWatcher = new QFutureWatcher<QInvidious::VideoListResult>(this); 0060 connect(playlistFutureWatcher, &QFutureWatcherBase::finished, this, [this, playlistFutureWatcher] { 0061 auto result = playlistFutureWatcher->result(); 0062 0063 if (const auto videoList = std::get_if<QList<QInvidious::VideoBasicInfo>>(&result)) { 0064 QStringList videoIdList; 0065 std::transform(videoList->cbegin(), videoList->cend(), std::back_inserter(videoIdList), [](const auto &video) { 0066 return video.videoId(); 0067 }); 0068 0069 replace(videoIdList); 0070 } else if (const auto error = std::get_if<QInvidious::Error>(&result)) { 0071 qDebug() << "VideoQueue::loadPlaylist(): Error:" << error->second << error->first; 0072 } 0073 0074 playlistFutureWatcher->deleteLater(); 0075 }); 0076 playlistFutureWatcher->setFuture(playlistFuture); 0077 } 0078 0079 void VideoQueue::next() 0080 { 0081 if (canGoNext()) { 0082 setCurrentIndex(m_currentIndex + 1); 0083 } 0084 } 0085 0086 bool VideoQueue::canGoNext() const 0087 { 0088 return m_currentIndex + 1 < m_videoIds.size(); 0089 } 0090 0091 void VideoQueue::previous() 0092 { 0093 if (canGoPrevious()) { 0094 m_currentIndex--; 0095 Q_EMIT currentVideoChanged(); 0096 } 0097 } 0098 0099 bool VideoQueue::canGoPrevious() const 0100 { 0101 return (m_currentIndex - 1) >= 0; 0102 } 0103 0104 QString VideoQueue::getCurrentVideoId() const 0105 { 0106 if (m_videoIds.isEmpty()) { 0107 return {}; 0108 } 0109 return m_videoIds[m_currentIndex]; 0110 } 0111 0112 int VideoQueue::rowCount(const QModelIndex &index) const 0113 { 0114 Q_UNUSED(index) 0115 return static_cast<int>(m_videoIds.size()); 0116 } 0117 0118 QVariant VideoQueue::data(const QModelIndex &index, int role) const 0119 { 0120 if (!checkIndex(index)) { 0121 return {}; 0122 } 0123 0124 const int row = index.row(); 0125 if (role == IdRole) { 0126 return m_videoIds[row]; 0127 } else if (role == PlayingRole) { 0128 return row == m_currentIndex; 0129 } 0130 0131 if (m_videoInfo[row] != std::nullopt) { 0132 const auto &video = *m_videoInfo[row]; 0133 switch (role) { 0134 case TitleRole: 0135 return video.title(); 0136 case ThumbnailRole: { 0137 const auto thumbnailUrl = video.thumbnail(QStringLiteral("medium")).url(); 0138 if (thumbnailUrl.isRelative()) { 0139 return QUrl(PlasmaTube::instance().sourceManager()->selectedSource()->api()->apiHost() + thumbnailUrl.toString(QUrl::FullyEncoded)); 0140 } 0141 return thumbnailUrl; 0142 } 0143 case LengthRole: 0144 return video.length(); 0145 case ViewCountRole: 0146 return video.viewCount(); 0147 case AuthorRole: 0148 return video.author(); 0149 case AuthorIdRole: 0150 return video.authorId(); 0151 case AuthorUrlRole: 0152 return video.authorUrl(); 0153 case PublishedRole: 0154 return video.published(); 0155 case PublishedTextRole: 0156 return video.publishedText(); 0157 case DescriptionRole: 0158 return video.description(); 0159 case DescriptionHtmlRole: 0160 return video.descriptionHtml(); 0161 case LiveNowRole: 0162 return video.liveNow(); 0163 case PaidRole: 0164 return video.paid(); 0165 case PremiumRole: 0166 return video.premium(); 0167 case WatchedRole: 0168 return PlasmaTube::instance().selectedSource()->isVideoWatched(video.videoId()); 0169 default: 0170 break; 0171 } 0172 } 0173 0174 return {}; 0175 } 0176 0177 QHash<int, QByteArray> VideoQueue::roleNames() const 0178 { 0179 return {{IdRole, "id"}, 0180 {TitleRole, "title"}, 0181 {ThumbnailRole, "thumbnail"}, 0182 {LengthRole, "length"}, 0183 {ViewCountRole, "viewCount"}, 0184 {AuthorRole, "author"}, 0185 {AuthorIdRole, "authorId"}, 0186 {AuthorUrlRole, "authorUrl"}, 0187 {PublishedRole, "published"}, 0188 {PublishedTextRole, "publishedText"}, 0189 {DescriptionRole, "description"}, 0190 {DescriptionHtmlRole, "descriptionHtml"}, 0191 {LiveNowRole, "liveNow"}, 0192 {PaidRole, "paid"}, 0193 {PremiumRole, "premium"}, 0194 {WatchedRole, "watched"}, 0195 {PlayingRole, "playing"}}; 0196 } 0197 0198 void VideoQueue::setCurrentIndex(const int newIndex) 0199 { 0200 const int oldIndex = m_currentIndex; 0201 m_currentIndex = newIndex; 0202 Q_EMIT currentVideoChanged(); 0203 0204 Q_EMIT dataChanged(index(oldIndex, 0), index(oldIndex, 0), {PlayingRole}); 0205 0206 Q_EMIT dataChanged(index(m_currentIndex, 0), index(m_currentIndex, 0), {PlayingRole}); 0207 } 0208 0209 void VideoQueue::requestMissingVideoInformation() 0210 { 0211 size_t i = 0; 0212 for (const auto &video : m_videoInfo) { 0213 // TODO: this doesn't cover the case where we could add another video while it's still fetchibng 0214 if (video == std::nullopt) { 0215 auto future = PlasmaTube::instance().sourceManager()->selectedSource()->api()->requestVideo(m_videoIds[i]); 0216 0217 auto watcher = new QFutureWatcher<QInvidious::VideoResult>(this); 0218 connect(watcher, &QFutureWatcherBase::finished, this, [this, watcher, i] { 0219 auto result = watcher->result(); 0220 0221 if (const auto video = std::get_if<QInvidious::Video>(&result)) { 0222 m_videoInfo[i] = *video; 0223 Q_EMIT dataChanged(index(i, 0), index(i, 0)); 0224 } else if (const auto error = std::get_if<QInvidious::Error>(&result)) { 0225 qDebug() << "VideoQueue::requestMissingVideoInformation(): Error:" << error->second << error->first; 0226 } 0227 0228 watcher->deleteLater(); 0229 }); 0230 watcher->setFuture(future); 0231 } 0232 i++; 0233 } 0234 } 0235 0236 bool VideoQueue::shouldBeVisible() const 0237 { 0238 return m_videoIds.size() > 1; 0239 } 0240 0241 #include "moc_videoqueue.cpp"