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("open-in-browser")
146 << The::globalActions()->value("findVideoParts")
147 << The::globalActions()->value("skip")
148 << The::globalActions()->value("previous")
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");
157 QSlider *slider = MainWindow::instance()->getSlider();
158 connect(slider, SIGNAL(valueChanged(int)), SLOT(sliderMoved(int)));
161 void MediaView::setMediaObject(Phonon::MediaObject *mediaObject) {
162 this->mediaObject = mediaObject;
163 Phonon::createPath(mediaObject, videoWidget);
164 connect(mediaObject, SIGNAL(finished()), SLOT(playbackFinished()));
165 connect(mediaObject, SIGNAL(stateChanged(Phonon::State, Phonon::State)),
166 SLOT(stateChanged(Phonon::State, Phonon::State)));
167 connect(mediaObject, SIGNAL(aboutToFinish()), SLOT(aboutToFinish()));
170 SearchParams* MediaView::getSearchParams() {
171 VideoSource *videoSource = playlistModel->getVideoSource();
172 if (videoSource && videoSource->metaObject()->className() == QLatin1String("YTSearch")) {
173 YTSearch *search = dynamic_cast<YTSearch *>(videoSource);
174 return search->getSearchParams();
179 void MediaView::search(SearchParams *searchParams) {
180 if (!searchParams->keywords().isEmpty()) {
181 if (searchParams->keywords().startsWith("http://") ||
182 searchParams->keywords().startsWith("https://")) {
183 QString videoId = YTSearch::videoIdFromUrl(searchParams->keywords());
184 if (!videoId.isEmpty()) {
185 YTSingleVideoSource *singleVideoSource = new YTSingleVideoSource(this);
186 singleVideoSource->setVideoId(videoId);
187 setVideoSource(singleVideoSource);
192 setVideoSource(new YTSearch(searchParams, this));
195 void MediaView::setVideoSource(VideoSource *videoSource, bool addToHistory) {
198 #ifdef APP_ACTIVATION
203 // qDebug() << "Adding VideoSource" << videoSource->getName() << videoSource;
206 int currentIndex = getHistoryIndex();
207 if (currentIndex >= 0 && currentIndex < history.size() - 1) {
208 while (history.size() > currentIndex + 1) {
209 VideoSource *vs = history.takeLast();
211 qDebug() << "Deleting VideoSource" << vs->getName() << vs;
216 history.append(videoSource);
219 playlistModel->setVideoSource(videoSource);
221 sidebar->showPlaylist();
222 sidebar->getRefineSearchWidget()->setSearchParams(getSearchParams());
223 sidebar->hideSuggestions();
224 sidebar->getHeader()->updateInfo();
226 SearchParams *searchParams = getSearchParams();
227 bool isChannel = searchParams && !searchParams->author().isEmpty();
228 playlistView->setClickableAuthors(!isChannel);
231 void MediaView::searchAgain() {
232 VideoSource *currentVideoSource = playlistModel->getVideoSource();
233 setVideoSource(currentVideoSource, false);
236 bool MediaView::canGoBack() {
237 return getHistoryIndex() > 0;
240 void MediaView::goBack() {
241 if (history.size() > 1) {
242 int currentIndex = getHistoryIndex();
243 if (currentIndex > 0) {
244 VideoSource *previousVideoSource = history.at(currentIndex - 1);
245 setVideoSource(previousVideoSource, false);
250 bool MediaView::canGoForward() {
251 int currentIndex = getHistoryIndex();
252 return currentIndex >= 0 && currentIndex < history.size() - 1;
255 void MediaView::goForward() {
256 if (canGoForward()) {
257 int currentIndex = getHistoryIndex();
258 VideoSource *nextVideoSource = history.at(currentIndex + 1);
259 setVideoSource(nextVideoSource, false);
263 int MediaView::getHistoryIndex() {
264 return history.lastIndexOf(playlistModel->getVideoSource());
267 void MediaView::appear() {
268 playlistView->setFocus();
269 Video *currentVideo = playlistModel->activeVideo();
271 MainWindow::instance()->setWindowTitle(
272 currentVideo->title() + " - " + Constants::NAME);
273 MainWindow::instance()->showMessage(currentVideo->description());
277 void MediaView::disappear() {
281 void MediaView::handleError(QString /* message */) {
282 QTimer::singleShot(500, this, SLOT(startPlaying()));
285 void MediaView::stateChanged(Phonon::State newState, Phonon::State /*oldState*/) {
286 if (newState == Phonon::PlayingState)
287 videoAreaWidget->showVideo();
288 else if (newState == Phonon::ErrorState) {
289 qDebug() << "Phonon error:" << mediaObject->errorString() << mediaObject->errorType();
290 if (mediaObject->errorType() == Phonon::FatalError)
291 handleError(mediaObject->errorString());
295 void MediaView::pause() {
296 switch( mediaObject->state() ) {
297 case Phonon::PlayingState:
298 mediaObject->pause();
306 QRegExp MediaView::wordRE(QString s) {
307 return QRegExp("\\W" + s + "\\W?", Qt::CaseInsensitive);
310 void MediaView::stop() {
313 while (!history.isEmpty()) {
314 VideoSource *videoSource = history.takeFirst();
315 if (!videoSource->parent()) delete videoSource;
318 playlistModel->abortSearch();
319 videoAreaWidget->clear();
320 videoAreaWidget->update();
322 playlistView->selectionModel()->clearSelection();
324 downloadItem->stop();
327 currentVideoSize = 0;
329 The::globalActions()->value("refine-search")->setChecked(false);
330 updateSubscriptionAction(0, false);
331 #ifdef APP_ACTIVATION
335 foreach (QAction *action, currentVideoActions)
336 action->setEnabled(false);
338 QAction *a = The::globalActions()->value("download");
339 a->setEnabled(false);
340 a->setVisible(false);
343 currentVideoId.clear();
345 QSlider *slider = MainWindow::instance()->getSlider();
346 slider->setEnabled(false);
350 const QString & MediaView::getCurrentVideoId() {
351 return currentVideoId;
354 void MediaView::activeRowChanged(int row) {
361 downloadItem->stop();
364 currentVideoSize = 0;
367 Video *video = playlistModel->videoAt(row);
370 videoAreaWidget->showLoading(video);
372 connect(video, SIGNAL(gotStreamUrl(QUrl)),
373 SLOT(gotStreamUrl(QUrl)), Qt::UniqueConnection);
374 connect(video, SIGNAL(errorStreamUrl(QString)),
375 SLOT(handleError(QString)), Qt::UniqueConnection);
376 video->loadStreamUrl();
378 // video title in titlebar
379 MainWindow::instance()->setWindowTitle(video->title() + " - " + Constants::NAME);
380 MainWindow::instance()->showMessage(video->description());
382 // ensure active item is visible
384 QModelIndex index = playlistModel->index(row, 0, QModelIndex());
385 playlistView->scrollTo(index, QAbstractItemView::EnsureVisible);
388 // enable/disable actions
389 The::globalActions()->value("download")->setEnabled(
390 DownloadManager::instance()->itemForVideo(video) == 0);
391 The::globalActions()->value("previous")->setEnabled(row > 0);
392 The::globalActions()->value("stopafterthis")->setEnabled(true);
393 The::globalActions()->value("related-videos")->setEnabled(true);
395 #ifndef APP_NO_DOWNLOADS
396 bool enableDownload = video->license() == Video::LicenseCC;
397 #ifdef APP_ACTIVATION
398 enableDownload = enableDownload || Activation::instance().isLegacy();
401 enableDownload = true;
403 QAction *a = The::globalActions()->value("download");
404 a->setEnabled(enableDownload);
405 a->setVisible(enableDownload);
408 updateSubscriptionAction(video, YTUser::isSubscribed(video->userId()));
410 foreach (QAction *action, currentVideoActions)
411 action->setEnabled(true);
413 QSlider *slider = MainWindow::instance()->getSlider();
414 slider->setEnabled(false);
417 // see you in gotStreamUrl...
420 void MediaView::gotStreamUrl(QUrl streamUrl) {
422 if (!streamUrl.isValid()) {
427 Video *video = static_cast<Video *>(sender());
429 qDebug() << "Cannot get sender in" << __PRETTY_FUNCTION__;
432 video->disconnect(this);
434 currentVideoId = video->id();
437 mediaObject->setCurrentSource(streamUrl);
443 // ensure we always have 10 videos ahead
444 playlistModel->searchNeeded();
446 // ensure active item is visible
447 int row = playlistModel->activeRow();
449 QModelIndex index = playlistModel->index(row, 0, QModelIndex());
450 playlistView->scrollTo(index, QAbstractItemView::EnsureVisible);
453 #ifdef APP_ACTIVATION
454 if (!Activation::instance().isActivated())
455 demoTimer->start(180000);
459 Extra::notify(video->title(), video->author(), video->formattedDuration());
462 ChannelAggregator::instance()->videoWatched(video);
465 void MediaView::downloadStatusChanged() {
466 // qDebug() << __PRETTY_FUNCTION__;
467 switch(downloadItem->status()) {
469 // qDebug() << "Downloading";
470 if (downloadItem->offset() == 0) startPlaying();
472 // qDebug() << "Seeking to" << downloadItem->offset();
473 mediaObject->seek(offsetToTime(downloadItem->offset()));
478 // qDebug() << "Starting";
481 // qDebug() << "Finished" << mediaObject->state();
483 // MainWindow::instance()->getSeekSlider()->setEnabled(mediaObject->isSeekable());
487 // qDebug() << "Failed";
491 // qDebug() << "Idle";
496 void MediaView::startPlaying() {
497 // qDebug() << __PRETTY_FUNCTION__;
504 if (downloadItem->offset() == 0) {
505 currentVideoSize = downloadItem->bytesTotal();
506 // qDebug() << "currentVideoSize" << currentVideoSize;
510 QString source = downloadItem->currentFilename();
511 qDebug() << "Playing" << source << QFile::exists(source);
512 mediaObject->setCurrentSource(source);
515 // MainWindow::instance()->getSeekSlider()->setEnabled(false);
518 QSlider *slider = MainWindow::instance()->getSlider();
519 slider->setEnabled(true);
522 void MediaView::itemActivated(const QModelIndex &index) {
523 if (playlistModel->rowExists(index.row())) {
525 // if it's the current video, just rewind and play
526 Video *activeVideo = playlistModel->activeVideo();
527 Video *video = playlistModel->videoAt(index.row());
528 if (activeVideo && video && activeVideo == video) {
529 // mediaObject->seek(0);
532 } else playlistModel->setActiveRow(index.row());
534 // the user doubleclicked on the "Search More" item
536 playlistModel->searchMore();
537 playlistView->selectionModel()->clearSelection();
541 void MediaView::skipVideo() {
542 // skippedVideo is useful for DELAYED skip operations
543 // in order to be sure that we're skipping the video we wanted
544 // and not another one
546 if (playlistModel->activeVideo() != skippedVideo) {
547 qDebug() << "Skip of video canceled";
550 int nextRow = playlistModel->rowForVideo(skippedVideo);
552 if (nextRow == -1) return;
553 playlistModel->setActiveRow(nextRow);
557 void MediaView::skip() {
558 int nextRow = playlistModel->nextRow();
559 if (nextRow == -1) return;
560 playlistModel->setActiveRow(nextRow);
563 void MediaView::skipBackward() {
564 int prevRow = playlistModel->previousRow();
565 if (prevRow == -1) return;
566 playlistModel->setActiveRow(prevRow);
569 void MediaView::aboutToFinish() {
570 qint64 currentTime = mediaObject->currentTime();
571 qint64 totalTime = mediaObject->totalTime();
572 qDebug() << __PRETTY_FUNCTION__ << currentTime << totalTime;
573 if (totalTime < 1 || currentTime + 10000 < totalTime) {
574 // QTimer::singleShot(500, this, SLOT(playbackResume()));
575 mediaObject->seek(currentTime);
580 void MediaView::playbackFinished() {
582 const qint64 totalTime = mediaObject->totalTime();
583 const qint64 currentTime = mediaObject->currentTime();
584 qDebug() << __PRETTY_FUNCTION__ << mediaObject->currentTime() << totalTime;
585 // add 10 secs for imprecise Phonon backends (VLC, Xine)
586 if (totalTime < 1 || (currentTime > 0 && currentTime + 10000 < totalTime)) {
587 // mediaObject->seek(currentTime);
588 QTimer::singleShot(500, this, SLOT(playbackResume()));
590 QAction* stopAfterThisAction = The::globalActions()->value("stopafterthis");
591 if (stopAfterThisAction->isChecked()) {
592 stopAfterThisAction->setChecked(false);
597 void MediaView::playbackResume() {
599 const qint64 currentTime = mediaObject->currentTime();
600 qDebug() << __PRETTY_FUNCTION__ << currentTime;
602 mediaObject->seek(currentTime);
606 void MediaView::openWebPage() {
607 Video* video = playlistModel->activeVideo();
609 mediaObject->pause();
610 QDesktopServices::openUrl(video->webpage());
613 void MediaView::copyWebPage() {
614 Video* video = playlistModel->activeVideo();
616 QString address = video->webpage().toString();
617 QApplication::clipboard()->setText(address);
618 QString message = tr("You can now paste the YouTube link into another application");
619 MainWindow::instance()->showMessage(message);
622 void MediaView::copyVideoLink() {
623 Video* video = playlistModel->activeVideo();
625 QApplication::clipboard()->setText(video->getStreamUrl().toEncoded());
626 QString message = tr("You can now paste the video stream URL into another application")
627 + ". " + tr("The link will be valid only for a limited time.");
628 MainWindow::instance()->showMessage(message);
631 void MediaView::openInBrowser() {
632 Video* video = playlistModel->activeVideo();
634 mediaObject->pause();
635 QDesktopServices::openUrl(video->getStreamUrl());
638 void MediaView::removeSelected() {
639 if (!playlistView->selectionModel()->hasSelection()) return;
640 QModelIndexList indexes = playlistView->selectionModel()->selectedIndexes();
641 playlistModel->removeIndexes(indexes);
644 void MediaView::selectVideos(QList<Video*> videos) {
645 foreach (Video *video, videos) {
646 QModelIndex index = playlistModel->indexForVideo(video);
647 playlistView->selectionModel()->select(index, QItemSelectionModel::Select);
648 playlistView->scrollTo(index, QAbstractItemView::EnsureVisible);
652 void MediaView::selectionChanged(const QItemSelection & /*selected*/,
653 const QItemSelection & /*deselected*/) {
654 const bool gotSelection = playlistView->selectionModel()->hasSelection();
655 The::globalActions()->value("remove")->setEnabled(gotSelection);
656 The::globalActions()->value("moveUp")->setEnabled(gotSelection);
657 The::globalActions()->value("moveDown")->setEnabled(gotSelection);
660 void MediaView::moveUpSelected() {
661 if (!playlistView->selectionModel()->hasSelection()) return;
663 QModelIndexList indexes = playlistView->selectionModel()->selectedIndexes();
664 qStableSort(indexes.begin(), indexes.end());
665 playlistModel->move(indexes, true);
667 // set current index after row moves to something more intuitive
668 int row = indexes.first().row();
669 playlistView->selectionModel()->setCurrentIndex(playlistModel->index(row>1?row:1),
670 QItemSelectionModel::NoUpdate);
673 void MediaView::moveDownSelected() {
674 if (!playlistView->selectionModel()->hasSelection()) return;
676 QModelIndexList indexes = playlistView->selectionModel()->selectedIndexes();
677 qStableSort(indexes.begin(), indexes.end(), qGreater<QModelIndex>());
678 playlistModel->move(indexes, false);
680 // set current index after row moves to something more intuitive
681 // (respect 1 static item on bottom)
682 int row = indexes.first().row()+1, max = playlistModel->rowCount() - 2;
683 playlistView->selectionModel()->setCurrentIndex(
684 playlistModel->index(row>max?max:row), QItemSelectionModel::NoUpdate);
687 void MediaView::setPlaylistVisible(bool visible) {
688 if (splitter->widget(0)->isVisible() == visible) return;
689 splitter->widget(0)->setVisible(visible);
690 playlistView->setFocus();
693 bool MediaView::isPlaylistVisible() {
694 return splitter->widget(0)->isVisible();
697 void MediaView::saveSplitterState() {
699 settings.setValue("splitter", splitter->saveState());
702 #ifdef APP_ACTIVATION
704 static QPushButton *continueButton;
706 void MediaView::demoMessage() {
707 if (mediaObject->state() != Phonon::PlayingState) return;
708 mediaObject->pause();
710 QMessageBox msgBox(this);
711 msgBox.setIconPixmap(QPixmap(":/images/app.png").scaled(64, 64, Qt::KeepAspectRatio, Qt::SmoothTransformation));
712 msgBox.setText(tr("This is just the demo version of %1.").arg(Constants::NAME));
713 msgBox.setInformativeText(tr("It allows you to test the application and see if it works for you."));
714 msgBox.setModal(true);
715 // make it a "sheet" on the Mac
716 msgBox.setWindowModality(Qt::WindowModal);
718 continueButton = msgBox.addButton("5", QMessageBox::RejectRole);
719 continueButton->setEnabled(false);
720 QPushButton *buyButton = msgBox.addButton(tr("Get the full version"), QMessageBox::ActionRole);
722 QTimeLine *timeLine = new QTimeLine(6000, this);
723 timeLine->setCurveShape(QTimeLine::LinearCurve);
724 timeLine->setFrameRange(5, 0);
725 connect(timeLine, SIGNAL(frameChanged(int)), SLOT(updateContinueButton(int)));
730 if (msgBox.clickedButton() == buyButton) {
731 MainWindow::instance()->showActivationView();
734 demoTimer->start(600000);
741 void MediaView::updateContinueButton(int value) {
743 continueButton->setText(tr("Continue"));
744 continueButton->setEnabled(true);
746 continueButton->setText(QString::number(value));
752 void MediaView::downloadVideo() {
753 Video* video = playlistModel->activeVideo();
755 DownloadManager::instance()->addItem(video);
756 The::globalActions()->value("downloads")->setVisible(true);
757 QString message = tr("Downloading %1").arg(video->title());
758 MainWindow::instance()->showMessage(message);
762 void MediaView::snapshot() {
763 QImage image = videoWidget->snapshot();
764 qDebug() << image.size();
766 const QPixmap& pixmap = QPixmap::grabWindow(videoWidget->winId());
767 // qDebug() << pixmap.size();
768 videoAreaWidget->showSnapshotPreview(pixmap);
772 void MediaView::fullscreen() {
773 videoAreaWidget->setParent(0);
774 videoAreaWidget->showFullScreen();
777 void MediaView::startDownloading() {
778 Video *video = playlistModel->activeVideo();
780 Video *videoCopy = video->clone();
782 downloadItem->stop();
785 QString tempFile = Temporary::filename();
786 downloadItem = new DownloadItem(videoCopy, video->getStreamUrl(), tempFile, this);
787 connect(downloadItem, SIGNAL(statusChanged()),
788 SLOT(downloadStatusChanged()), Qt::UniqueConnection);
789 connect(downloadItem, SIGNAL(bufferProgress(int)),
790 loadingWidget, SLOT(bufferStatus(int)), Qt::UniqueConnection);
791 // connect(downloadItem, SIGNAL(finished()), SLOT(itemFinished()));
792 connect(video, SIGNAL(errorStreamUrl(QString)),
793 SLOT(handleError(QString)), Qt::UniqueConnection);
794 connect(downloadItem, SIGNAL(error(QString)),
795 SLOT(handleError(QString)), Qt::UniqueConnection);
796 downloadItem->start();
799 void MediaView::sliderMoved(int value) {
800 if (currentVideoSize <= 0 || !downloadItem || !mediaObject->isSeekable())
803 QSlider *slider = MainWindow::instance()->getSlider();
804 if (slider->isSliderDown()) return;
806 qint64 offset = (currentVideoSize * value) / slider->maximum();
808 bool needsDownload = downloadItem->needsDownload(offset);
810 if (downloadItem->isBuffered(offset)) {
811 qint64 realOffset = downloadItem->blankAtOffset(offset);
812 if (offset < currentVideoSize)
813 downloadItem->seekTo(realOffset, false);
814 mediaObject->seek(offsetToTime(offset));
816 mediaObject->pause();
817 downloadItem->seekTo(offset);
820 // qDebug() << "simple seek";
821 mediaObject->seek(offsetToTime(offset));
825 qint64 MediaView::offsetToTime(qint64 offset) {
826 const qint64 totalTime = mediaObject->totalTime();
827 return ((offset * totalTime) / currentVideoSize);
830 void MediaView::findVideoParts() {
833 Video* video = playlistModel->activeVideo();
836 QString query = video->title();
838 static QString optionalSpace = "\\s*";
839 static QString staticCounterSeparators = "[\\/\\-]";
840 QString counterSeparators = "( of | " +
841 tr("of", "Used in video parts, as in '2 of 3'") +
842 " |" + staticCounterSeparators + ")";
844 // numbers from 1 to 15
845 static QString counterNumber = "([1-9]|1[0-5])";
847 // query.remove(QRegExp(counterSeparators + optionalSpace + counterNumber));
848 query.remove(QRegExp(counterNumber + optionalSpace +
849 counterSeparators + optionalSpace + counterNumber));
850 query.remove(wordRE("pr?t\\.?" + optionalSpace + counterNumber));
851 query.remove(wordRE("ep\\.?" + optionalSpace + counterNumber));
852 query.remove(wordRE("part" + optionalSpace + counterNumber));
853 query.remove(wordRE("episode" + optionalSpace + counterNumber));
854 query.remove(wordRE(tr("part", "This is for video parts, as in 'Cool video - part 1'") +
855 optionalSpace + counterNumber));
856 query.remove(wordRE(tr("episode",
857 "This is for video parts, as in 'Cool series - episode 1'") +
858 optionalSpace + counterNumber));
859 query.remove(QRegExp("[\\(\\)\\[\\]]"));
861 #define NUMBERS "one|two|three|four|five|six|seven|eight|nine|ten"
863 QRegExp englishNumberRE = QRegExp(QLatin1String(".*(") + NUMBERS + ").*",
864 Qt::CaseInsensitive);
865 // bool numberAsWords = englishNumberRE.exactMatch(query);
866 query.remove(englishNumberRE);
868 QRegExp localizedNumberRE = QRegExp(QLatin1String(".*(") + tr(NUMBERS) + ").*",
869 Qt::CaseInsensitive);
870 // if (!numberAsWords) numberAsWords = localizedNumberRE.exactMatch(query);
871 query.remove(localizedNumberRE);
873 SearchParams *searchParams = new SearchParams();
874 searchParams->setTransient(true);
875 searchParams->setKeywords(query);
876 searchParams->setAuthor(video->author());
879 if (!numberAsWords) {
880 qDebug() << "We don't have number as words";
881 // searchParams->setSortBy(SearchParams::SortByNewest);
882 // TODO searchParams->setReverseOrder(true);
883 // TODO searchParams->setMax(50);
887 search(searchParams);
891 void MediaView::relatedVideos() {
892 Video* video = playlistModel->activeVideo();
894 YTSingleVideoSource *singleVideoSource = new YTSingleVideoSource();
895 singleVideoSource->setVideoId(video->id());
896 setVideoSource(singleVideoSource);
897 The::globalActions()->value("related-videos")->setEnabled(false);
900 void MediaView::shareViaTwitter() {
901 Video* video = playlistModel->activeVideo();
903 QUrl url("https://twitter.com/intent/tweet");
904 url.addQueryItem("via", "minitubeapp");
905 url.addQueryItem("text", video->title());
906 url.addQueryItem("url", video->webpage().toString());
907 QDesktopServices::openUrl(url);
910 void MediaView::shareViaFacebook() {
911 Video* video = playlistModel->activeVideo();
913 QUrl url("https://www.facebook.com/sharer.php");
914 url.addQueryItem("t", video->title());
915 url.addQueryItem("u", video->webpage().toString());
916 QDesktopServices::openUrl(url);
919 void MediaView::shareViaBuffer() {
920 Video* video = playlistModel->activeVideo();
922 QUrl url("http://bufferapp.com/add");
923 url.addQueryItem("via", "minitubeapp");
924 url.addQueryItem("text", video->title());
925 url.addQueryItem("url", video->webpage().toString());
926 url.addQueryItem("picture", video->thumbnailUrl());
927 QDesktopServices::openUrl(url);
930 void MediaView::shareViaEmail() {
931 Video* video = playlistModel->activeVideo();
934 url.addQueryItem("subject", video->title());
935 QString body = video->title() + "\n" +
936 video->webpage().toString() + "\n\n" +
937 tr("Sent from %1").arg(Constants::NAME) + "\n" +
939 url.addQueryItem("body", body);
940 QDesktopServices::openUrl(url);
943 void MediaView::authorPushed(QModelIndex index) {
944 Video* video = playlistModel->videoAt(index.row());
947 QString channel = video->userId();
948 if (channel.isEmpty()) channel = video->author();
949 if (channel.isEmpty()) return;
951 SearchParams *searchParams = new SearchParams();
952 searchParams->setAuthor(channel);
953 searchParams->setSortBy(SearchParams::SortByNewest);
956 search(searchParams);
959 void MediaView::updateSubscriptionAction(Video *video, bool subscribed) {
960 QAction *subscribeAction = The::globalActions()->value("subscribe-channel");
962 QString subscribeTip;
963 QString subscribeText;
965 subscribeText = subscribeAction->property("originalText").toString();
966 subscribeAction->setEnabled(false);
967 } else if (subscribed) {
968 subscribeText = tr("Unsubscribe from %1").arg(video->author());
969 subscribeTip = subscribeText;
970 subscribeAction->setEnabled(true);
972 subscribeText = tr("Subscribe to %1").arg(video->author());
973 subscribeTip = subscribeText;
974 subscribeAction->setEnabled(true);
976 subscribeAction->setText(subscribeText);
977 subscribeAction->setStatusTip(subscribeTip);
981 static QIcon tintedIcon;
982 if (tintedIcon.isNull()) {
984 sizes << QSize(16, 16);
985 tintedIcon = Utils::tintedIcon("bookmark-new", QColor(254, 240, 0), sizes);
987 subscribeAction->setIcon(tintedIcon);
989 subscribeAction->setIcon(Utils::icon("bookmark-remove"));
992 subscribeAction->setIcon(Utils::icon("bookmark-new"));
995 Utils::setupAction(subscribeAction);
998 void MediaView::toggleSubscription() {
999 Video *video = playlistModel->activeVideo();
1001 QString userId = video->userId();
1002 if (userId.isEmpty()) return;
1003 bool subscribed = YTUser::isSubscribed(userId);
1004 if (subscribed) YTUser::unsubscribe(userId);
1005 else YTUser::subscribe(userId);
1006 updateSubscriptionAction(video, !subscribed);