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/>.
21 #include "mediaview.h"
22 #include "playlistmodel.h"
23 #include "playlistview.h"
24 #include "loadingwidget.h"
25 #include "videoareawidget.h"
26 #include "networkaccess.h"
27 #include "videowidget.h"
28 #include "minisplitter.h"
29 #include "constants.h"
30 #include "downloadmanager.h"
31 #include "downloaditem.h"
32 #include "mainwindow.h"
33 #include "temporary.h"
34 #include "refinesearchwidget.h"
35 #include "sidebarwidget.h"
36 #include "sidebarheader.h"
38 #include "activation.h"
43 #include "videosource.h"
45 #include "searchparams.h"
46 #include "ytsinglevideosource.h"
47 #include "channelaggregator.h"
52 NetworkAccess* http();
53 QHash<QString, QAction*>* globalActions();
54 QHash<QString, QMenu*>* globalMenus();
55 QNetworkAccessManager* networkAccessManager();
58 MediaView* MediaView::instance() {
59 static MediaView *i = new MediaView();
63 MediaView::MediaView(QWidget *parent) : QWidget(parent),
67 void MediaView::initialize() {
68 QBoxLayout *layout = new QVBoxLayout(this);
71 splitter = new MiniSplitter();
73 playlistView = new PlaylistView(this);
74 // respond to the user doubleclicking a playlist item
75 connect(playlistView, SIGNAL(activated(const QModelIndex &)),
76 SLOT(itemActivated(const QModelIndex &)));
78 playlistModel = new PlaylistModel();
79 connect(playlistModel, SIGNAL(activeRowChanged(int)),
80 SLOT(activeRowChanged(int)));
81 // needed to restore the selection after dragndrop
82 connect(playlistModel, SIGNAL(needSelectionFor(QList<Video*>)),
83 SLOT(selectVideos(QList<Video*>)));
84 playlistView->setModel(playlistModel);
86 connect(playlistView->selectionModel(),
87 SIGNAL(selectionChanged(const QItemSelection &, const QItemSelection &)),
88 SLOT(selectionChanged(const QItemSelection &, const QItemSelection &)));
90 connect(playlistView, SIGNAL(authorPushed(QModelIndex)), SLOT(authorPushed(QModelIndex)));
92 sidebar = new SidebarWidget(this);
93 sidebar->setPlaylist(playlistView);
94 connect(sidebar->getRefineSearchWidget(), SIGNAL(searchRefined()),
96 connect(playlistModel, SIGNAL(haveSuggestions(const QStringList &)),
97 sidebar, SLOT(showSuggestions(const QStringList &)));
98 connect(sidebar, SIGNAL(suggestionAccepted(QString)),
99 MainWindow::instance(), SLOT(startToolbarSearch(QString)));
100 splitter->addWidget(sidebar);
102 videoAreaWidget = new VideoAreaWidget(this);
103 // videoAreaWidget->setMinimumSize(320,240);
104 videoWidget = new Phonon::VideoWidget(this);
105 videoAreaWidget->setVideoWidget(videoWidget);
106 videoAreaWidget->setListModel(playlistModel);
108 loadingWidget = new LoadingWidget(this);
109 videoAreaWidget->setLoadingWidget(loadingWidget);
111 splitter->addWidget(videoAreaWidget);
113 splitter->setStretchFactor(0, 0);
114 splitter->setStretchFactor(1, 8);
116 // restore splitter state
118 splitter->restoreState(settings.value("splitter").toByteArray());
119 splitter->setChildrenCollapsible(false);
121 layout->addWidget(splitter);
123 errorTimer = new QTimer(this);
124 errorTimer->setSingleShot(true);
125 errorTimer->setInterval(3000);
126 connect(errorTimer, SIGNAL(timeout()), SLOT(skipVideo()));
128 #ifdef APP_ACTIVATION
129 demoTimer = new QTimer(this);
130 demoTimer->setSingleShot(true);
131 connect(demoTimer, SIGNAL(timeout()), SLOT(demoMessage()));
134 connect(videoAreaWidget, SIGNAL(doubleClicked()),
135 The::globalActions()->value("fullscreen"), SLOT(trigger()));
137 QAction* refineSearchAction = The::globalActions()->value("refine-search");
138 connect(refineSearchAction, SIGNAL(toggled(bool)),
139 sidebar, SLOT(toggleRefineSearch(bool)));
142 << The::globalActions()->value("webpage")
143 << The::globalActions()->value("pagelink")
144 << The::globalActions()->value("videolink")
145 << The::globalActions()->value("findVideoParts")
146 << The::globalActions()->value("skip")
147 << The::globalActions()->value("previous")
148 // << The::globalActions()->value("download")
149 << The::globalActions()->value("stopafterthis")
150 << The::globalActions()->value("related-videos")
151 << The::globalActions()->value("refine-search")
152 << The::globalActions()->value("twitter")
153 << The::globalActions()->value("facebook")
154 << The::globalActions()->value("buffer")
155 << The::globalActions()->value("email");
158 void MediaView::setMediaObject(Phonon::MediaObject *mediaObject) {
159 this->mediaObject = mediaObject;
160 Phonon::createPath(mediaObject, videoWidget);
161 connect(mediaObject, SIGNAL(finished()), SLOT(playbackFinished()));
162 connect(mediaObject, SIGNAL(stateChanged(Phonon::State, Phonon::State)),
163 SLOT(stateChanged(Phonon::State, Phonon::State)));
165 const char* s = Constants::NAME;
166 const int l = strlen(s);
167 int t = The::globalActions()->count();
168 for (int i = 0; i < l; i++) {
170 qDebug() << t << The::globalActions()->count();
172 qDebug() << t << The::globalActions()->count();
173 if (t != s[0]) return;
175 connect(mediaObject, SIGNAL(aboutToFinish()), SLOT(aboutToFinish()));
178 SearchParams* MediaView::getSearchParams() {
179 VideoSource *videoSource = playlistModel->getVideoSource();
180 if (videoSource && videoSource->metaObject()->className() == QLatin1String("YTSearch")) {
181 YTSearch *search = dynamic_cast<YTSearch *>(videoSource);
182 return search->getSearchParams();
187 void MediaView::search(SearchParams *searchParams) {
188 if (!searchParams->keywords().isEmpty()) {
189 if (searchParams->keywords().startsWith("http://") ||
190 searchParams->keywords().startsWith("https://")) {
191 QString videoId = YTSearch::videoIdFromUrl(searchParams->keywords());
192 if (!videoId.isEmpty()) {
193 YTSingleVideoSource *singleVideoSource = new YTSingleVideoSource(this);
194 singleVideoSource->setVideoId(videoId);
195 setVideoSource(singleVideoSource);
200 setVideoSource(new YTSearch(searchParams, this));
203 void MediaView::setVideoSource(VideoSource *videoSource, bool addToHistory) {
206 #ifdef APP_ACTIVATION
211 // qDebug() << "Adding VideoSource" << videoSource->getName() << videoSource;
214 int currentIndex = getHistoryIndex();
215 if (currentIndex >= 0 && currentIndex < history.size() - 1) {
216 while (history.size() > currentIndex + 1) {
217 VideoSource *vs = history.takeLast();
219 qDebug() << "Deleting VideoSource" << vs->getName() << vs;
224 history.append(videoSource);
227 playlistModel->setVideoSource(videoSource);
229 sidebar->showPlaylist();
230 sidebar->getRefineSearchWidget()->setSearchParams(getSearchParams());
231 sidebar->hideSuggestions();
232 sidebar->getHeader()->updateInfo();
234 SearchParams *searchParams = getSearchParams();
235 bool isChannel = searchParams && !searchParams->author().isEmpty();
236 playlistView->setClickableAuthors(!isChannel);
239 void MediaView::searchAgain() {
240 VideoSource *currentVideoSource = playlistModel->getVideoSource();
241 setVideoSource(currentVideoSource, false);
244 bool MediaView::canGoBack() {
245 return getHistoryIndex() > 0;
248 void MediaView::goBack() {
249 if (history.size() > 1) {
250 int currentIndex = getHistoryIndex();
251 if (currentIndex > 0) {
252 VideoSource *previousVideoSource = history.at(currentIndex - 1);
253 setVideoSource(previousVideoSource, false);
258 bool MediaView::canGoForward() {
259 int currentIndex = getHistoryIndex();
260 return currentIndex >= 0 && currentIndex < history.size() - 1;
263 void MediaView::goForward() {
264 if (canGoForward()) {
265 int currentIndex = getHistoryIndex();
266 VideoSource *nextVideoSource = history.at(currentIndex + 1);
267 setVideoSource(nextVideoSource, false);
271 int MediaView::getHistoryIndex() {
272 return history.lastIndexOf(playlistModel->getVideoSource());
275 void MediaView::appear() {
276 playlistView->setFocus();
277 Video *currentVideo = playlistModel->activeVideo();
279 MainWindow::instance()->setWindowTitle(
280 currentVideo->title() + " - " + Constants::NAME);
281 MainWindow::instance()->showMessage(currentVideo->description());
285 void MediaView::disappear() {
289 void MediaView::handleError(QString /* message */) {
290 QTimer::singleShot(500, this, SLOT(startPlaying()));
293 void MediaView::stateChanged(Phonon::State newState, Phonon::State /*oldState*/) {
294 if (newState == Phonon::PlayingState)
295 videoAreaWidget->showVideo();
296 else if (newState == Phonon::ErrorState) {
297 qDebug() << "Phonon error:" << mediaObject->errorString() << mediaObject->errorType();
298 if (mediaObject->errorType() == Phonon::FatalError)
299 handleError(mediaObject->errorString());
303 void MediaView::pause() {
304 switch( mediaObject->state() ) {
305 case Phonon::PlayingState:
306 mediaObject->pause();
314 QRegExp MediaView::wordRE(QString s) {
315 return QRegExp("\\W" + s + "\\W?", Qt::CaseInsensitive);
318 void MediaView::stop() {
321 while (!history.isEmpty()) {
322 VideoSource *videoSource = history.takeFirst();
323 if (!videoSource->parent()) delete videoSource;
326 playlistModel->abortSearch();
327 videoAreaWidget->clear();
328 videoAreaWidget->update();
330 playlistView->selectionModel()->clearSelection();
332 downloadItem->stop();
336 The::globalActions()->value("refine-search")->setChecked(false);
337 updateSubscriptionAction(0, false);
338 #ifdef APP_ACTIVATION
342 foreach (QAction *action, currentVideoActions)
343 action->setEnabled(false);
345 QAction *a = The::globalActions()->value("download");
346 a->setEnabled(false);
347 a->setVisible(false);
350 currentVideoId.clear();
353 const QString & MediaView::getCurrentVideoId() {
354 return currentVideoId;
357 void MediaView::activeRowChanged(int row) {
364 downloadItem->stop();
369 Video *video = playlistModel->videoAt(row);
372 videoAreaWidget->showLoading(video);
374 connect(video, SIGNAL(gotStreamUrl(QUrl)),
375 SLOT(gotStreamUrl(QUrl)), Qt::UniqueConnection);
376 connect(video, SIGNAL(errorStreamUrl(QString)),
377 SLOT(handleError(QString)), Qt::UniqueConnection);
378 video->loadStreamUrl();
380 // video title in titlebar
381 MainWindow::instance()->setWindowTitle(video->title() + " - " + Constants::NAME);
382 MainWindow::instance()->showMessage(video->description());
384 // ensure active item is visible
386 QModelIndex index = playlistModel->index(row, 0, QModelIndex());
387 playlistView->scrollTo(index, QAbstractItemView::EnsureVisible);
390 // enable/disable actions
391 The::globalActions()->value("download")->setEnabled(
392 DownloadManager::instance()->itemForVideo(video) == 0);
393 // The::globalActions()->value("skip")->setEnabled(true);
394 The::globalActions()->value("previous")->setEnabled(row > 0);
395 The::globalActions()->value("stopafterthis")->setEnabled(true);
396 The::globalActions()->value("related-videos")->setEnabled(true);
398 #ifndef APP_NO_DOWNLOADS
399 bool enableDownload = video->license() == Video::LicenseCC;
400 #ifdef APP_ACTIVATION
401 enableDownload = enableDownload || Activation::instance().isLegacy();
404 enableDownload = true;
406 QAction *a = The::globalActions()->value("download");
407 a->setEnabled(enableDownload);
408 a->setVisible(enableDownload);
411 updateSubscriptionAction(video, YTUser::isSubscribed(video->userId()));
413 foreach (QAction *action, currentVideoActions)
414 action->setEnabled(true);
416 // see you in gotStreamUrl...
419 void MediaView::gotStreamUrl(QUrl streamUrl) {
421 if (!streamUrl.isValid()) {
426 Video *video = static_cast<Video *>(sender());
428 qDebug() << "Cannot get sender in" << __PRETTY_FUNCTION__;
431 video->disconnect(this);
433 currentVideoId = video->id();
436 mediaObject->setCurrentSource(streamUrl);
439 QString tempFile = Temporary::filename();
440 Video *videoCopy = video->clone();
442 downloadItem->stop();
445 downloadItem = new DownloadItem(videoCopy, streamUrl, tempFile, this);
446 connect(downloadItem, SIGNAL(statusChanged()),
447 SLOT(downloadStatusChanged()), Qt::UniqueConnection);
448 // connect(downloadItem, SIGNAL(progress(int)), SLOT(downloadProgress(int)));
449 connect(downloadItem, SIGNAL(bufferProgress(int)),
450 loadingWidget, SLOT(bufferStatus(int)), Qt::UniqueConnection);
451 // connect(downloadItem, SIGNAL(finished()), SLOT(itemFinished()));
452 connect(video, SIGNAL(errorStreamUrl(QString)),
453 SLOT(handleError(QString)), Qt::UniqueConnection);
454 connect(downloadItem, SIGNAL(error(QString)),
455 SLOT(handleError(QString)), Qt::UniqueConnection);
456 downloadItem->start();
459 // ensure we always have 10 videos ahead
460 playlistModel->searchNeeded();
462 // ensure active item is visible
463 int row = playlistModel->activeRow();
465 QModelIndex index = playlistModel->index(row, 0, QModelIndex());
466 playlistView->scrollTo(index, QAbstractItemView::EnsureVisible);
469 #ifdef APP_ACTIVATION
470 if (!Activation::instance().isActivated())
471 demoTimer->start(180000);
475 Extra::notify(video->title(), video->author(), video->formattedDuration());
478 ChannelAggregator::instance()->videoWatched(video);
482 void MediaView::downloadProgress(int percent) {
483 MainWindow* mainWindow = dynamic_cast<MainWindow*>(window());
485 mainWindow->getSeekSlider()->setStyleSheet(" QSlider::groove:horizontal {"
486 "border: 1px solid #999999;"
487 // "border-left: 50px solid rgba(255, 0, 0, 128);"
489 "background: qlineargradient(x1:0, y1:0, x2:.5, y2:0, stop:0 rgba(255, 0, 0, 92), stop:"
490 + QString::number(percent/100.0) +
492 " rgba(255, 0, 0, 92), stop:" + QString::number((percent+1)/100.0) + " transparent, stop:1 transparent);"
495 "QSlider::handle:horizontal {"
496 "background: qlineargradient(x1:0, y1:0, x2:1, y2:1, stop:0 #b4b4b4, stop:1 #8f8f8f);"
497 "border: 1px solid #5c5c5c;"
501 "border-radius: 8px;"
508 void MediaView::downloadStatusChanged() {
509 switch(downloadItem->status()) {
514 // qDebug() << "Starting";
517 // qDebug() << "Finished" << mediaObject->state();
518 // if (mediaObject->state() == Phonon::StoppedState) startPlaying();
520 MainWindow::instance()->getSeekSlider()->setEnabled(mediaObject->isSeekable());
524 // qDebug() << "Failed";
526 // qDebug() << "Idle";
531 void MediaView::startPlaying() {
539 QString source = downloadItem->currentFilename();
540 // qDebug() << "Playing" << source;
541 mediaObject->setCurrentSource(source);
544 MainWindow::instance()->getSeekSlider()->setEnabled(false);
549 void MediaView::itemActivated(const QModelIndex &index) {
550 if (playlistModel->rowExists(index.row())) {
552 // if it's the current video, just rewind and play
553 Video *activeVideo = playlistModel->activeVideo();
554 Video *video = playlistModel->videoAt(index.row());
555 if (activeVideo && video && activeVideo == video) {
556 mediaObject->seek(0);
558 } else playlistModel->setActiveRow(index.row());
560 // the user doubleclicked on the "Search More" item
562 playlistModel->searchMore();
563 playlistView->selectionModel()->clearSelection();
567 void MediaView::skipVideo() {
568 // skippedVideo is useful for DELAYED skip operations
569 // in order to be sure that we're skipping the video we wanted
570 // and not another one
572 if (playlistModel->activeVideo() != skippedVideo) {
573 qDebug() << "Skip of video canceled";
576 int nextRow = playlistModel->rowForVideo(skippedVideo);
578 if (nextRow == -1) return;
579 playlistModel->setActiveRow(nextRow);
583 void MediaView::skip() {
584 int nextRow = playlistModel->nextRow();
585 if (nextRow == -1) return;
586 playlistModel->setActiveRow(nextRow);
589 void MediaView::skipBackward() {
590 int prevRow = playlistModel->previousRow();
591 if (prevRow == -1) return;
592 playlistModel->setActiveRow(prevRow);
595 void MediaView::aboutToFinish() {
596 qint64 currentTime = mediaObject->currentTime();
597 qint64 totalTime = mediaObject->totalTime();
598 qDebug() << __PRETTY_FUNCTION__ << currentTime << totalTime;
599 if (totalTime < 1 || currentTime + 10000 < totalTime) {
600 // mediaObject->seek(mediaObject->currentTime());
601 // QTimer::singleShot(500, this, SLOT(playbackResume()));
602 mediaObject->seek(currentTime);
607 void MediaView::playbackFinished() {
609 const int totalTime = mediaObject->totalTime();
610 const int currentTime = mediaObject->currentTime();
611 qDebug() << __PRETTY_FUNCTION__ << mediaObject->currentTime() << totalTime;
612 // add 10 secs for imprecise Phonon backends (VLC, Xine)
613 if (totalTime < 1 || (currentTime > 0 && currentTime + 10000 < totalTime)) {
614 // mediaObject->seek(currentTime);
615 QTimer::singleShot(500, this, SLOT(playbackResume()));
617 QAction* stopAfterThisAction = The::globalActions()->value("stopafterthis");
618 if (stopAfterThisAction->isChecked()) {
619 stopAfterThisAction->setChecked(false);
624 void MediaView::playbackResume() {
626 qDebug() << __PRETTY_FUNCTION__ << mediaObject->currentTime();
627 mediaObject->seek(mediaObject->currentTime());
631 void MediaView::openWebPage() {
632 Video* video = playlistModel->activeVideo();
634 mediaObject->pause();
635 QDesktopServices::openUrl(video->webpage());
638 void MediaView::copyWebPage() {
639 Video* video = playlistModel->activeVideo();
641 QString address = video->webpage().toString();
642 QApplication::clipboard()->setText(address);
643 QString message = tr("You can now paste the YouTube link into another application");
644 MainWindow::instance()->showMessage(message);
647 void MediaView::copyVideoLink() {
648 Video* video = playlistModel->activeVideo();
650 QApplication::clipboard()->setText(video->getStreamUrl().toEncoded());
651 QString message = tr("You can now paste the video stream URL into another application")
652 + ". " + tr("The link will be valid only for a limited time.");
653 MainWindow::instance()->showMessage(message);
656 void MediaView::removeSelected() {
657 if (!playlistView->selectionModel()->hasSelection()) return;
658 QModelIndexList indexes = playlistView->selectionModel()->selectedIndexes();
659 playlistModel->removeIndexes(indexes);
662 void MediaView::selectVideos(QList<Video*> videos) {
663 foreach (Video *video, videos) {
664 QModelIndex index = playlistModel->indexForVideo(video);
665 playlistView->selectionModel()->select(index, QItemSelectionModel::Select);
666 playlistView->scrollTo(index, QAbstractItemView::EnsureVisible);
670 void MediaView::selectionChanged(const QItemSelection & /*selected*/,
671 const QItemSelection & /*deselected*/) {
672 const bool gotSelection = playlistView->selectionModel()->hasSelection();
673 The::globalActions()->value("remove")->setEnabled(gotSelection);
674 The::globalActions()->value("moveUp")->setEnabled(gotSelection);
675 The::globalActions()->value("moveDown")->setEnabled(gotSelection);
678 void MediaView::moveUpSelected() {
679 if (!playlistView->selectionModel()->hasSelection()) return;
681 QModelIndexList indexes = playlistView->selectionModel()->selectedIndexes();
682 qStableSort(indexes.begin(), indexes.end());
683 playlistModel->move(indexes, true);
685 // set current index after row moves to something more intuitive
686 int row = indexes.first().row();
687 playlistView->selectionModel()->setCurrentIndex(playlistModel->index(row>1?row:1),
688 QItemSelectionModel::NoUpdate);
691 void MediaView::moveDownSelected() {
692 if (!playlistView->selectionModel()->hasSelection()) return;
694 QModelIndexList indexes = playlistView->selectionModel()->selectedIndexes();
695 qStableSort(indexes.begin(), indexes.end(), qGreater<QModelIndex>());
696 playlistModel->move(indexes, false);
698 // set current index after row moves to something more intuitive
699 // (respect 1 static item on bottom)
700 int row = indexes.first().row()+1, max = playlistModel->rowCount() - 2;
701 playlistView->selectionModel()->setCurrentIndex(
702 playlistModel->index(row>max?max:row), QItemSelectionModel::NoUpdate);
705 void MediaView::setPlaylistVisible(bool visible) {
706 if (splitter->widget(0)->isVisible() == visible) return;
707 splitter->widget(0)->setVisible(visible);
708 playlistView->setFocus();
711 bool MediaView::isPlaylistVisible() {
712 return splitter->widget(0)->isVisible();
715 void MediaView::saveSplitterState() {
717 settings.setValue("splitter", splitter->saveState());
720 #ifdef APP_ACTIVATION
722 static QPushButton *continueButton;
724 void MediaView::demoMessage() {
725 if (mediaObject->state() != Phonon::PlayingState) return;
726 mediaObject->pause();
728 QMessageBox msgBox(this);
729 msgBox.setIconPixmap(QPixmap(":/images/app.png").scaled(64, 64, Qt::KeepAspectRatio, Qt::SmoothTransformation));
730 msgBox.setText(tr("This is just the demo version of %1.").arg(Constants::NAME));
731 msgBox.setInformativeText(tr("It allows you to test the application and see if it works for you."));
732 msgBox.setModal(true);
733 // make it a "sheet" on the Mac
734 msgBox.setWindowModality(Qt::WindowModal);
736 continueButton = msgBox.addButton("5", QMessageBox::RejectRole);
737 continueButton->setEnabled(false);
738 QPushButton *buyButton = msgBox.addButton(tr("Get the full version"), QMessageBox::ActionRole);
740 QTimeLine *timeLine = new QTimeLine(6000, this);
741 timeLine->setCurveShape(QTimeLine::LinearCurve);
742 timeLine->setFrameRange(5, 0);
743 connect(timeLine, SIGNAL(frameChanged(int)), SLOT(updateContinueButton(int)));
748 if (msgBox.clickedButton() == buyButton) {
749 MainWindow::instance()->showActivationView();
752 demoTimer->start(600000);
759 void MediaView::updateContinueButton(int value) {
761 continueButton->setText(tr("Continue"));
762 continueButton->setEnabled(true);
764 continueButton->setText(QString::number(value));
770 void MediaView::downloadVideo() {
771 Video* video = playlistModel->activeVideo();
773 DownloadManager::instance()->addItem(video);
774 The::globalActions()->value("downloads")->setVisible(true);
775 QString message = tr("Downloading %1").arg(video->title());
776 MainWindow::instance()->showMessage(message);
780 void MediaView::snapshot() {
781 QImage image = videoWidget->snapshot();
782 qDebug() << image.size();
784 const QPixmap& pixmap = QPixmap::grabWindow(videoWidget->winId());
785 // qDebug() << pixmap.size();
786 videoAreaWidget->showSnapshotPreview(pixmap);
790 void MediaView::fullscreen() {
791 videoAreaWidget->setParent(0);
792 videoAreaWidget->showFullScreen();
796 void MediaView::setSlider(QSlider *slider) {
797 this->slider = slider;
798 // slider->setEnabled(false);
799 slider->setTracking(false);
800 // connect(slider, SIGNAL(valueChanged(int)), SLOT(sliderMoved(int)));
803 void MediaView::sliderMoved(int value) {
804 qDebug() << __func__;
805 int sliderPercent = (value * 100) / (slider->maximum() - slider->minimum());
806 qDebug() << slider->minimum() << value << slider->maximum();
807 if (sliderPercent <= downloadItem->currentPercent()) {
808 qDebug() << sliderPercent << downloadItem->currentPercent();
809 mediaObject->seek(value);
815 void MediaView::seekTo(int value) {
816 qDebug() << __func__;
817 mediaObject->pause();
819 // mediaObject->clear();
821 QString tempDir = QDesktopServices::storageLocation(QDesktopServices::TempLocation);
822 QString tempFile = tempDir + "/minitube" + QString::number(value) + ".mp4";
823 if (!QFile::remove(tempFile)) {
824 qDebug() << "Cannot remove temp file";
826 Video *videoCopy = downloadItem->getVideo()->clone();
827 QUrl streamUrl = videoCopy->getStreamUrl();
828 streamUrl.addQueryItem("begin", QString::number(value));
829 if (downloadItem) delete downloadItem;
830 downloadItem = new DownloadItem(videoCopy, streamUrl, tempFile, this);
831 connect(downloadItem, SIGNAL(statusChanged()), SLOT(downloadStatusChanged()));
832 // connect(downloadItem, SIGNAL(finished()), SLOT(itemFinished()));
833 downloadItem->start();
835 // slider->setMinimum(value);
841 void MediaView::findVideoParts() {
844 Video* video = playlistModel->activeVideo();
847 QString query = video->title();
849 static QString optionalSpace = "\\s*";
850 static QString staticCounterSeparators = "[\\/\\-]";
851 QString counterSeparators = "( of | " +
852 tr("of", "Used in video parts, as in '2 of 3'") +
853 " |" + staticCounterSeparators + ")";
855 // numbers from 1 to 15
856 static QString counterNumber = "([1-9]|1[0-5])";
858 // query.remove(QRegExp(counterSeparators + optionalSpace + counterNumber));
859 query.remove(QRegExp(counterNumber + optionalSpace +
860 counterSeparators + optionalSpace + counterNumber));
861 query.remove(wordRE("pr?t\\.?" + optionalSpace + counterNumber));
862 query.remove(wordRE("ep\\.?" + optionalSpace + counterNumber));
863 query.remove(wordRE("part" + optionalSpace + counterNumber));
864 query.remove(wordRE("episode" + optionalSpace + counterNumber));
865 query.remove(wordRE(tr("part", "This is for video parts, as in 'Cool video - part 1'") +
866 optionalSpace + counterNumber));
867 query.remove(wordRE(tr("episode",
868 "This is for video parts, as in 'Cool series - episode 1'") +
869 optionalSpace + counterNumber));
870 query.remove(QRegExp("[\\(\\)\\[\\]]"));
872 #define NUMBERS "one|two|three|four|five|six|seven|eight|nine|ten"
874 QRegExp englishNumberRE = QRegExp(QLatin1String(".*(") + NUMBERS + ").*",
875 Qt::CaseInsensitive);
876 // bool numberAsWords = englishNumberRE.exactMatch(query);
877 query.remove(englishNumberRE);
879 QRegExp localizedNumberRE = QRegExp(QLatin1String(".*(") + tr(NUMBERS) + ").*",
880 Qt::CaseInsensitive);
881 // if (!numberAsWords) numberAsWords = localizedNumberRE.exactMatch(query);
882 query.remove(localizedNumberRE);
884 SearchParams *searchParams = new SearchParams();
885 searchParams->setTransient(true);
886 searchParams->setKeywords(query);
887 searchParams->setAuthor(video->author());
890 if (!numberAsWords) {
891 qDebug() << "We don't have number as words";
892 // searchParams->setSortBy(SearchParams::SortByNewest);
893 // TODO searchParams->setReverseOrder(true);
894 // TODO searchParams->setMax(50);
898 search(searchParams);
902 void MediaView::relatedVideos() {
903 Video* video = playlistModel->activeVideo();
905 YTSingleVideoSource *singleVideoSource = new YTSingleVideoSource();
906 singleVideoSource->setVideoId(video->id());
907 setVideoSource(singleVideoSource);
908 The::globalActions()->value("related-videos")->setEnabled(false);
911 void MediaView::shareViaTwitter() {
912 Video* video = playlistModel->activeVideo();
914 QUrl url("https://twitter.com/intent/tweet");
915 url.addQueryItem("via", "minitubeapp");
916 url.addQueryItem("text", video->title());
917 url.addQueryItem("url", video->webpage().toString());
918 QDesktopServices::openUrl(url);
921 void MediaView::shareViaFacebook() {
922 Video* video = playlistModel->activeVideo();
924 QUrl url("https://www.facebook.com/sharer.php");
925 url.addQueryItem("t", video->title());
926 url.addQueryItem("u", video->webpage().toString());
927 QDesktopServices::openUrl(url);
930 void MediaView::shareViaBuffer() {
931 Video* video = playlistModel->activeVideo();
933 QUrl url("http://bufferapp.com/add");
934 url.addQueryItem("via", "minitubeapp");
935 url.addQueryItem("text", video->title());
936 url.addQueryItem("url", video->webpage().toString());
937 url.addQueryItem("picture", video->thumbnailUrl());
938 QDesktopServices::openUrl(url);
941 void MediaView::shareViaEmail() {
942 Video* video = playlistModel->activeVideo();
945 url.addQueryItem("subject", video->title());
946 QString body = video->title() + "\n" +
947 video->webpage().toString() + "\n\n" +
948 tr("Sent from %1").arg(Constants::NAME) + "\n" +
950 url.addQueryItem("body", body);
951 QDesktopServices::openUrl(url);
954 void MediaView::authorPushed(QModelIndex index) {
955 Video* video = playlistModel->videoAt(index.row());
958 QString channel = video->userId();
959 if (channel.isEmpty()) channel = video->author();
960 if (channel.isEmpty()) return;
962 SearchParams *searchParams = new SearchParams();
963 searchParams->setAuthor(channel);
964 searchParams->setSortBy(SearchParams::SortByNewest);
967 search(searchParams);
970 void MediaView::updateSubscriptionAction(Video *video, bool subscribed) {
971 QAction *subscribeAction = The::globalActions()->value("subscribe-channel");
973 QString subscribeTip;
974 QString subscribeText;
976 subscribeText = subscribeAction->property("originalText").toString();
977 subscribeAction->setEnabled(false);
978 } else if (subscribed) {
979 subscribeText = tr("Unsubscribe from %1").arg(video->author());
980 subscribeTip = subscribeText;
981 subscribeAction->setEnabled(true);
983 subscribeText = tr("Subscribe to %1").arg(video->author());
984 subscribeTip = subscribeText;
985 subscribeAction->setEnabled(true);
987 subscribeAction->setText(subscribeText);
988 subscribeAction->setStatusTip(subscribeTip);
992 static QIcon tintedIcon;
993 if (tintedIcon.isNull()) {
995 sizes << QSize(16, 16);
996 tintedIcon = Utils::tintedIcon("bookmark-new", QColor(254, 240, 0), sizes);
998 subscribeAction->setIcon(tintedIcon);
1000 subscribeAction->setIcon(Utils::icon("bookmark-remove"));
1003 subscribeAction->setIcon(Utils::icon("bookmark-new"));
1006 Utils::setupAction(subscribeAction);
1009 void MediaView::toggleSubscription() {
1010 Video *video = playlistModel->activeVideo();
1012 QString userId = video->userId();
1013 if (userId.isEmpty()) return;
1014 bool subscribed = YTUser::isSubscribed(userId);
1015 if (subscribed) YTUser::unsubscribe(userId);
1016 else YTUser::subscribe(userId);
1017 updateSubscriptionAction(video, !subscribed);