3 This file is part of Minitube.
4 Copyright 2009, Flavio Tordini <flavio.tordini@gmail.com>
6 Minitube is free software: you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation, either version 3 of the License, or
9 (at your option) any later version.
11 Minitube is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with Minitube. If not, see <http://www.gnu.org/licenses/>.
22 #include "datautils.h"
24 #include "httputils.h"
25 #include "jsfunctions.h"
26 #include "playlistitemdelegate.h"
27 #include "videodefinition.h"
29 #include "ytjsvideo.h"
32 #include "variantpromise.h"
35 : duration(0), viewCount(-1), license(LicenseYouTube), definitionCode(0), ytVideo(nullptr),
39 qDebug() << "Deleting" << id;
42 Video *Video::clone() {
43 Video *clone = new Video();
45 clone->description = description;
46 clone->channelTitle = channelTitle;
47 clone->channelId = channelId;
48 clone->webpage = webpage;
49 clone->streamUrl = streamUrl;
50 clone->thumbs = thumbs;
51 clone->thumbsNeedSorting = thumbsNeedSorting;
52 clone->duration = duration;
53 clone->formattedDuration = formattedDuration;
54 clone->published = published;
55 clone->formattedPublished = formattedPublished;
56 clone->viewCount = viewCount;
57 clone->formattedViewCount = formattedViewCount;
59 clone->definitionCode = definitionCode;
63 const QString &Video::getWebpage() {
64 if (webpage.isEmpty() && !id.isEmpty())
65 webpage.append("https://www.youtube.com/watch?v=").append(id);
69 void Video::setWebpage(const QString &value) {
74 QRegExp re(JsFunctions::instance()->videoIdRE());
75 if (re.indexIn(webpage) == -1) {
76 qWarning() << QString("Cannot get video id for %1").arg(webpage);
77 // emit errorStreamUrl(QString("Cannot get video id for %1").arg(m_webpage.toString()));
78 // loadingStreamUrl = false;
85 void Video::setDuration(int value) {
87 formattedDuration = DataUtils::formatDuration(duration);
90 void Video::setViewCount(int value) {
92 formattedViewCount = DataUtils::formatCount(viewCount);
95 void Video::setPublished(const QDateTime &value) {
97 formattedPublished = DataUtils::formatDateTime(published);
100 void Video::streamUrlLoaded(const QString &streamUrl, const QString &audioUrl) {
101 qDebug() << "Streams loaded";
102 this->streamUrl = streamUrl;
103 emit gotStreamUrl(streamUrl, audioUrl);
105 definitionCode = ytVideo->getDefinitionCode();
106 ytVideo->deleteLater();
110 definitionCode = ytjsVideo->getDefinitionCode();
111 ytjsVideo->deleteLater();
116 void Video::loadStreamUrlJS() {
118 qDebug() << "Already loading" << id;
121 ytjsVideo = new YTJSVideo(id, this);
122 connect(ytjsVideo, &YTJSVideo::gotStreamUrl, this, &Video::streamUrlLoaded);
123 connect(ytjsVideo, &YTJSVideo::errorStreamUrl, this, [this](const QString &msg) {
125 ytjsVideo->deleteLater();
127 // loadStreamUrlYT();
128 emit errorStreamUrl(msg);
130 ytjsVideo->loadStreamUrl();
133 void Video::loadStreamUrlYT() {
135 qDebug() << "Already loading" << id;
138 ytVideo = new YTVideo(id, this);
139 connect(ytVideo, &YTVideo::gotStreamUrl, this, &Video::streamUrlLoaded);
140 connect(ytVideo, &YTVideo::errorStreamUrl, this, [this](const QString &msg) {
142 emit errorStreamUrl(msg);
143 ytVideo->deleteLater();
146 ytVideo->loadStreamUrl();
149 void Video::loadStreamUrl() {
153 bool Video::isLoadingStreamUrl() const {
154 return ytVideo != nullptr || ytjsVideo != nullptr;
157 void Video::abortLoadStreamUrl() {
159 ytVideo->disconnect(this);
160 ytVideo->deleteLater();
164 ytjsVideo->disconnect(this);
165 ytjsVideo->deleteLater();
170 void Video::addThumb(int width, int height, QString url) {
171 thumbs << YTThumb(width, height, url);
172 thumbsNeedSorting = true;
175 VariantPromise &Video::loadThumb(QSize size, qreal pixelRatio) {
176 if (thumbsNeedSorting) {
177 std::sort(thumbs.begin(), thumbs.end(),
178 [](auto a, auto b) { return a.getWidth() < b.getWidth(); });
179 thumbsNeedSorting = false;
182 auto promise = new VariantPromise(this);
183 if (thumbs.isEmpty()) {
184 QTimer::singleShot(0, promise, [promise] { promise->reject("Empty thumbs"); });
188 auto reallyLoad = [this, promise, size, pixelRatio](auto &&self,
189 YTThumb *previous = nullptr) -> void {
190 YTThumb *selected = nullptr;
192 static bool fallback = false;
194 qDebug() << "Doing fallback loop";
195 bool skip = previous != nullptr;
196 for (int i = thumbs.size() - 1; i >= 0; --i) {
197 auto &thumb = thumbs.at(i);
199 selected = (YTThumb *)&thumb;
200 qDebug() << "selected" << selected->getUrl();
203 if (&thumb == previous) skip = false;
206 bool skip = previous != nullptr;
207 for (auto &thumb : qAsConst(thumbs)) {
208 if (!skip && thumb.getWidth() * pixelRatio >= size.width() &&
209 thumb.getHeight() * pixelRatio >= size.height()) {
210 selected = (YTThumb *)&thumb;
211 qDebug() << "selected" << selected->getUrl();
214 if (&thumb == previous) skip = false;
217 if (!selected && !fallback) {
218 qDebug() << "Falling back";
224 qDebug() << "Loading" << selected->getUrl();
225 selected->load(promise)
226 .then([promise](auto variant) { promise->resolve(variant); })
227 .onFailed([self, selected] { self(self, selected); });
229 promise->reject("No thumb");
231 reallyLoad(reallyLoad);