]> git.sur5r.net Git - minitube/blob - src/mediaview.cpp
Related videos action
[minitube] / src / mediaview.cpp
1 #include "mediaview.h"
2 #include "playlistmodel.h"
3 #include "playlistview.h"
4 #include "loadingwidget.h"
5 #include "videoareawidget.h"
6 #include "networkaccess.h"
7 #include "videowidget.h"
8 #include "minisplitter.h"
9 #include "constants.h"
10 #include "downloadmanager.h"
11 #include "downloaditem.h"
12 #include "mainwindow.h"
13 #include "temporary.h"
14 #include "refinesearchwidget.h"
15 #include "sidebarwidget.h"
16 #include "sidebarheader.h"
17 #ifdef APP_MAC
18 #include "macfullscreen.h"
19 #include "macutils.h"
20 #endif
21 #ifdef APP_ACTIVATION
22 #include "activation.h"
23 #endif
24 #include "videosource.h"
25 #include "ytsearch.h"
26 #include "searchparams.h"
27 #include "ytsinglevideosource.h"
28
29 namespace The {
30 NetworkAccess* http();
31 QMap<QString, QAction*>* globalActions();
32 QMap<QString, QMenu*>* globalMenus();
33 QNetworkAccessManager* networkAccessManager();
34 }
35
36 MediaView* MediaView::instance() {
37     static MediaView *i = new MediaView();
38     return i;
39 }
40
41 MediaView::MediaView(QWidget *parent) : QWidget(parent) {
42     reallyStopped = false;
43     downloadItem = 0;
44
45     QBoxLayout *layout = new QVBoxLayout(this);
46     layout->setMargin(0);
47
48     splitter = new MiniSplitter();
49     splitter->setChildrenCollapsible(false);
50
51     playlistView = new PlaylistView(this);
52     // respond to the user doubleclicking a playlist item
53     connect(playlistView, SIGNAL(activated(const QModelIndex &)),
54             SLOT(itemActivated(const QModelIndex &)));
55
56     playlistModel = new PlaylistModel();
57     connect(playlistModel, SIGNAL(activeRowChanged(int)),
58             SLOT(activeRowChanged(int)));
59     // needed to restore the selection after dragndrop
60     connect(playlistModel, SIGNAL(needSelectionFor(QList<Video*>)),
61             SLOT(selectVideos(QList<Video*>)));
62     playlistView->setModel(playlistModel);
63
64     connect(playlistView->selectionModel(),
65             SIGNAL(selectionChanged(const QItemSelection &, const QItemSelection &)),
66             SLOT(selectionChanged(const QItemSelection &, const QItemSelection &)));
67
68     connect(playlistView, SIGNAL(authorPushed(QModelIndex)), SLOT(authorPushed(QModelIndex)));
69
70     sidebar = new SidebarWidget(this);
71     sidebar->setPlaylist(playlistView);
72     connect(sidebar->getRefineSearchWidget(), SIGNAL(searchRefined()),
73             SLOT(searchAgain()));
74     connect(playlistModel, SIGNAL(haveSuggestions(const QStringList &)),
75             sidebar, SLOT(showSuggestions(const QStringList &)));
76     connect(sidebar, SIGNAL(suggestionAccepted(QString)),
77             MainWindow::instance(), SLOT(startToolbarSearch(QString)));
78     splitter->addWidget(sidebar);
79
80     videoAreaWidget = new VideoAreaWidget(this);
81     videoAreaWidget->setMinimumSize(320,240);
82     videoWidget = new Phonon::VideoWidget(this);
83     videoAreaWidget->setVideoWidget(videoWidget);
84     videoAreaWidget->setListModel(playlistModel);
85
86     loadingWidget = new LoadingWidget(this);
87     videoAreaWidget->setLoadingWidget(loadingWidget);
88
89     splitter->addWidget(videoAreaWidget);
90
91     layout->addWidget(splitter);
92
93     splitter->setStretchFactor(0, 1);
94     splitter->setStretchFactor(1, 6);
95
96     // restore splitter state
97     QSettings settings;
98     splitter->restoreState(settings.value("splitter").toByteArray());
99
100     errorTimer = new QTimer(this);
101     errorTimer->setSingleShot(true);
102     errorTimer->setInterval(3000);
103     connect(errorTimer, SIGNAL(timeout()), SLOT(skipVideo()));
104
105 #ifdef APP_ACTIVATION
106     demoTimer = new QTimer(this);
107     demoTimer->setSingleShot(true);
108     connect(demoTimer, SIGNAL(timeout()), SLOT(demoMessage()));
109 #endif
110
111 }
112
113 void MediaView::initialize() {
114     connect(videoAreaWidget, SIGNAL(doubleClicked()),
115             The::globalActions()->value("fullscreen"), SLOT(trigger()));
116
117     QAction* refineSearchAction = The::globalActions()->value("refine-search");
118     connect(refineSearchAction, SIGNAL(toggled(bool)),
119             sidebar, SLOT(toggleRefineSearch(bool)));
120 }
121
122 void MediaView::setMediaObject(Phonon::MediaObject *mediaObject) {
123     this->mediaObject = mediaObject;
124     Phonon::createPath(mediaObject, videoWidget);
125     connect(mediaObject, SIGNAL(finished()), SLOT(playbackFinished()));
126     connect(mediaObject, SIGNAL(stateChanged(Phonon::State, Phonon::State)),
127             SLOT(stateChanged(Phonon::State, Phonon::State)));
128     connect(mediaObject, SIGNAL(currentSourceChanged(Phonon::MediaSource)),
129             SLOT(currentSourceChanged(Phonon::MediaSource)));
130     connect(mediaObject, SIGNAL(aboutToFinish()), SLOT(aboutToFinish()));
131 }
132
133 SearchParams* MediaView::getSearchParams() {
134     VideoSource *videoSource = playlistModel->getVideoSource();
135     if (videoSource && videoSource->metaObject()->className() == QLatin1String("YTSearch")) {
136         YTSearch *search = dynamic_cast<YTSearch *>(videoSource);
137         return search->getSearchParams();
138     }
139     return 0;
140 }
141
142 void MediaView::search(SearchParams *searchParams) {
143     if (!searchParams->keywords().isEmpty()) {
144         if (searchParams->keywords().startsWith("http://") ||
145                 searchParams->keywords().startsWith("https://")) {
146             QString videoId = YTSearch::videoIdFromUrl(searchParams->keywords());
147             if (!videoId.isEmpty()) {
148                 qDebug() << "single video";
149                 YTSingleVideoSource *singleVideoSource = new YTSingleVideoSource(this);
150                 singleVideoSource->setVideoId(videoId);
151                 setVideoSource(singleVideoSource);
152                 return;
153             }
154         }
155     }
156     setVideoSource(new YTSearch(searchParams, this));
157 }
158
159 void MediaView::setVideoSource(VideoSource *videoSource, bool addToHistory) {
160     reallyStopped = false;
161
162 #ifdef APP_ACTIVATION
163     demoTimer->stop();
164 #endif
165     errorTimer->stop();
166
167     if (addToHistory) {
168         int currentIndex = getHistoryIndex();
169         if (currentIndex >= 0 && currentIndex < history.size() - 1) {
170             while (history.size() > currentIndex + 1) {
171                 VideoSource *vs = history.takeLast();
172                 if (!vs->parent()) delete vs;
173             }
174         }
175         history.append(videoSource);
176     }
177
178     playlistModel->setVideoSource(videoSource);
179
180     sidebar->showPlaylist();
181     sidebar->getRefineSearchWidget()->setSearchParams(getSearchParams());
182     sidebar->hideSuggestions();
183     sidebar->getHeader()->updateInfo();
184
185     SearchParams *searchParams = getSearchParams();
186     bool isChannel = searchParams && !searchParams->author().isEmpty();
187     playlistView->setClickableAuthors(!isChannel);
188 }
189
190 void MediaView::searchAgain() {
191     VideoSource *currentVideoSource = playlistModel->getVideoSource();
192     setVideoSource(currentVideoSource, false);
193 }
194
195 bool MediaView::canGoBack() {
196     return getHistoryIndex() > 0;
197 }
198
199 void MediaView::goBack() {
200     if (history.size() > 1) {
201         int currentIndex = getHistoryIndex();
202         if (currentIndex > 0) {
203             VideoSource *previousVideoSource = history.at(currentIndex - 1);
204             setVideoSource(previousVideoSource, false);
205         }
206     }
207 }
208
209 bool MediaView::canGoForward() {
210     int currentIndex = getHistoryIndex();
211     return currentIndex >= 0 && currentIndex < history.size() - 1;
212 }
213
214 void MediaView::goForward() {
215     if (canGoForward()) {
216         int currentIndex = getHistoryIndex();
217         VideoSource *nextVideoSource = history.at(currentIndex + 1);
218         setVideoSource(nextVideoSource, false);
219     }
220 }
221
222 int MediaView::getHistoryIndex() {
223     return history.lastIndexOf(playlistModel->getVideoSource());
224 }
225
226 void MediaView::appear() {
227     playlistView->setFocus();
228 }
229
230 void MediaView::disappear() {
231
232 }
233
234 void MediaView::handleError(QString /* message */) {
235     QTimer::singleShot(500, this, SLOT(startPlaying()));
236 }
237
238 void MediaView::stateChanged(Phonon::State newState, Phonon::State /*oldState*/) {
239     if (newState == Phonon::PlayingState)
240         videoAreaWidget->showVideo();
241     else if (newState == Phonon::ErrorState) {
242         qDebug() << "Phonon error:" << mediaObject->errorString() << mediaObject->errorType();
243         if (mediaObject->errorType() == Phonon::FatalError)
244             handleError(mediaObject->errorString());
245     }
246 }
247
248 void MediaView::pause() {
249     switch( mediaObject->state() ) {
250     case Phonon::PlayingState:
251         mediaObject->pause();
252         break;
253     default:
254         mediaObject->play();
255         break;
256     }
257 }
258
259 QRegExp MediaView::wordRE(QString s) {
260     return QRegExp("\\W" + s + "\\W?", Qt::CaseInsensitive);
261 }
262
263 void MediaView::stop() {
264     playlistModel->abortSearch();
265     reallyStopped = true;
266     mediaObject->stop();
267     videoAreaWidget->clear();
268     errorTimer->stop();
269     playlistView->selectionModel()->clearSelection();
270     if (downloadItem) {
271         downloadItem->stop();
272         delete downloadItem;
273         downloadItem = 0;
274     }
275     The::globalActions()->value("refine-search")->setChecked(false);
276
277     while (!history.isEmpty()) {
278         VideoSource *videoSource = history.takeFirst();
279         if (!videoSource->parent()) delete videoSource;
280     }
281 }
282
283 void MediaView::activeRowChanged(int row) {
284     if (reallyStopped) return;
285
286     Video *video = playlistModel->videoAt(row);
287     if (!video) return;
288
289     // Do not stop/start playing if the first video result is the current video
290     if (row == 0 && !history.isEmpty()) {
291         Video *currentVideo = 0;
292         if (downloadItem && downloadItem->getVideo())
293             currentVideo = downloadItem->getVideo();
294         if (currentVideo && playlistModel->videoAt(row)->webpage() == currentVideo->webpage()) {
295             The::globalActions()->value("related-videos")->setEnabled(false);
296             return;
297         }
298     }
299
300     errorTimer->stop();
301
302     videoAreaWidget->showLoading(video);
303
304     mediaObject->stop();
305     if (downloadItem) {
306         downloadItem->stop();
307         delete downloadItem;
308         downloadItem = 0;
309     }
310
311     connect(video, SIGNAL(gotStreamUrl(QUrl)),
312             SLOT(gotStreamUrl(QUrl)), Qt::UniqueConnection);
313     connect(video, SIGNAL(errorStreamUrl(QString)),
314             SLOT(handleError(QString)), Qt::UniqueConnection);
315
316     video->loadStreamUrl();
317
318     // video title in the statusbar
319     MainWindow::instance()->showMessage(video->title());
320
321     // ensure active item is visible
322     if (row != -1) {
323         QModelIndex index = playlistModel->index(row, 0, QModelIndex());
324         playlistView->scrollTo(index, QAbstractItemView::EnsureVisible);
325     }
326
327     // enable/disable actions
328     The::globalActions()->value("download")->setEnabled(
329                 DownloadManager::instance()->itemForVideo(video) == 0);
330     The::globalActions()->value("skip")->setEnabled(true);
331     The::globalActions()->value("previous")->setEnabled(row > 0);
332     The::globalActions()->value("stopafterthis")->setEnabled(true);
333     The::globalActions()->value("related-videos")->setEnabled(true);
334
335     // see you in gotStreamUrl...
336
337 }
338
339 void MediaView::gotStreamUrl(QUrl streamUrl) {
340     if (reallyStopped) return;
341
342     Video *video = static_cast<Video *>(sender());
343     if (!video) {
344         qDebug() << "Cannot get sender in" << __PRETTY_FUNCTION__;
345         return;
346     }
347     video->disconnect(this);
348
349     QString tempFile = Temporary::filename();
350
351     Video *videoCopy = video->clone();
352     if (downloadItem) {
353         downloadItem->stop();
354         delete downloadItem;
355     }
356     downloadItem = new DownloadItem(videoCopy, streamUrl, tempFile, this);
357     connect(downloadItem, SIGNAL(statusChanged()),
358             SLOT(downloadStatusChanged()), Qt::UniqueConnection);
359     // connect(downloadItem, SIGNAL(progress(int)), SLOT(downloadProgress(int)));
360     connect(downloadItem, SIGNAL(bufferProgress(int)),
361             loadingWidget, SLOT(bufferStatus(int)), Qt::UniqueConnection);
362     // connect(downloadItem, SIGNAL(finished()), SLOT(itemFinished()));
363     connect(video, SIGNAL(errorStreamUrl(QString)),
364             SLOT(handleError(QString)), Qt::UniqueConnection);
365     connect(downloadItem, SIGNAL(error(QString)),
366             SLOT(handleError(QString)), Qt::UniqueConnection);
367     downloadItem->start();
368
369 #ifdef Q_WS_MAC
370     if (mac::canNotify())
371         mac::notify(video->title(), video->author(), video->formattedDuration());
372 #endif
373 }
374
375 /*
376 void MediaView::downloadProgress(int percent) {
377     MainWindow* mainWindow = dynamic_cast<MainWindow*>(window());
378
379     mainWindow->getSeekSlider()->setStyleSheet(" QSlider::groove:horizontal {"
380         "border: 1px solid #999999;"
381         // "border-left: 50px solid rgba(255, 0, 0, 128);"
382         "height: 8px;"
383         "background: qlineargradient(x1:0, y1:0, x2:.5, y2:0, stop:0 rgba(255, 0, 0, 92), stop:"
384         + QString::number(percent/100.0) +
385
386         " rgba(255, 0, 0, 92), stop:" + QString::number((percent+1)/100.0) + " transparent, stop:1 transparent);"
387         "margin: 2px 0;"
388     "}"
389     "QSlider::handle:horizontal {"
390         "background: qlineargradient(x1:0, y1:0, x2:1, y2:1, stop:0 #b4b4b4, stop:1 #8f8f8f);"
391         "border: 1px solid #5c5c5c;"
392         "width: 16px;"
393         "height: 16px;"
394         "margin: -2px 0;"
395         "border-radius: 8px;"
396     "}"
397
398     );
399 }
400
401 */
402
403 void MediaView::downloadStatusChanged() {
404     switch(downloadItem->status()) {
405     case Downloading:
406         startPlaying();
407         break;
408     case Starting:
409         // qDebug() << "Starting";
410         break;
411     case Finished:
412         // qDebug() << "Finished" << mediaObject->state();
413         // if (mediaObject->state() == Phonon::StoppedState) startPlaying();
414 #ifdef Q_WS_X11
415         MainWindow::instance()->getSeekSlider()->setEnabled(mediaObject->isSeekable());
416 #endif
417         break;
418     case Failed:
419         // qDebug() << "Failed";
420     case Idle:
421         // qDebug() << "Idle";
422         break;
423     }
424 }
425
426 void MediaView::startPlaying() {
427     if (reallyStopped) return;
428     if (!downloadItem) {
429         skip();
430         return;
431     }
432
433     // go!
434     QString source = downloadItem->currentFilename();
435     qDebug() << "Playing" << source;
436     mediaObject->setCurrentSource(source);
437     mediaObject->play();
438 #ifdef Q_WS_X11
439     MainWindow::instance()->getSeekSlider()->setEnabled(false);
440 #endif
441
442     // ensure we always have 10 videos ahead
443     playlistModel->searchNeeded();
444
445     // ensure active item is visible
446     int row = playlistModel->activeRow();
447     if (row != -1) {
448         QModelIndex index = playlistModel->index(row, 0, QModelIndex());
449         playlistView->scrollTo(index, QAbstractItemView::EnsureVisible);
450     }
451
452 #ifdef APP_ACTIVATION
453     if (!Activation::instance().isActivated())
454         demoTimer->start(180000);
455 #endif
456
457 }
458
459 void MediaView::itemActivated(const QModelIndex &index) {
460     if (playlistModel->rowExists(index.row())) {
461
462         // if it's the current video, just rewind and play
463         Video *activeVideo = playlistModel->activeVideo();
464         Video *video = playlistModel->videoAt(index.row());
465         if (activeVideo && video && activeVideo == video) {
466             mediaObject->seek(0);
467             mediaObject->play();
468         } else playlistModel->setActiveRow(index.row());
469
470         // the user doubleclicked on the "Search More" item
471     } else {
472         playlistModel->searchMore();
473         playlistView->selectionModel()->clearSelection();
474     }
475 }
476
477 void MediaView::currentSourceChanged(const Phonon::MediaSource /* source */ ) {
478
479 }
480
481 void MediaView::skipVideo() {
482     // skippedVideo is useful for DELAYED skip operations
483     // in order to be sure that we're skipping the video we wanted
484     // and not another one
485     if (skippedVideo) {
486         if (playlistModel->activeVideo() != skippedVideo) {
487             qDebug() << "Skip of video canceled";
488             return;
489         }
490         int nextRow = playlistModel->rowForVideo(skippedVideo);
491         nextRow++;
492         if (nextRow == -1) return;
493         playlistModel->setActiveRow(nextRow);
494     }
495 }
496
497 void MediaView::skip() {
498     int nextRow = playlistModel->nextRow();
499     if (nextRow == -1) return;
500     playlistModel->setActiveRow(nextRow);
501 }
502
503 void MediaView::skipBackward() {
504     int prevRow = playlistModel->previousRow();
505     if (prevRow == -1) return;
506     playlistModel->setActiveRow(prevRow);
507 }
508
509 void MediaView::aboutToFinish() {
510     qint64 currentTime = mediaObject->currentTime();
511     qDebug() << __PRETTY_FUNCTION__ << currentTime;
512     if (currentTime + 10000 < mediaObject->totalTime()) {
513         // mediaObject->seek(mediaObject->currentTime());
514         // QTimer::singleShot(500, this, SLOT(playbackResume()));
515         mediaObject->seek(currentTime);
516         mediaObject->play();
517     }
518 }
519
520 void MediaView::playbackFinished() {
521     qDebug() << __PRETTY_FUNCTION__ << mediaObject->currentTime();
522     // qDebug() << "finished" << mediaObject->currentTime() << mediaObject->totalTime();
523     // add 10 secs for imprecise Phonon backends (VLC, Xine)
524     if (mediaObject->currentTime() + 10000 < mediaObject->totalTime()) {
525         // mediaObject->seek(mediaObject->currentTime());
526         QTimer::singleShot(500, this, SLOT(playbackResume()));
527     } else {
528         QAction* stopAfterThisAction = The::globalActions()->value("stopafterthis");
529         if (stopAfterThisAction->isChecked()) {
530             stopAfterThisAction->setChecked(false);
531         } else skip();
532     }
533 }
534
535 void MediaView::playbackResume() {
536     qDebug() << __PRETTY_FUNCTION__ << mediaObject->currentTime();
537     mediaObject->seek(mediaObject->currentTime());
538     mediaObject->play();
539 }
540
541 void MediaView::openWebPage() {
542     Video* video = playlistModel->activeVideo();
543     if (!video) return;
544     mediaObject->pause();
545     QDesktopServices::openUrl(video->webpage());
546 }
547
548 void MediaView::copyWebPage() {
549     Video* video = playlistModel->activeVideo();
550     if (!video) return;
551     QString address = video->webpage().toString();
552     QApplication::clipboard()->setText(address);
553     QString message = tr("You can now paste the YouTube link into another application");
554     MainWindow::instance()->showMessage(message);
555 }
556
557 void MediaView::copyVideoLink() {
558     Video* video = playlistModel->activeVideo();
559     if (!video) return;
560     QApplication::clipboard()->setText(video->getStreamUrl().toEncoded());
561     QString message = tr("You can now paste the video stream URL into another application")
562             + ". " + tr("The link will be valid only for a limited time.");
563     MainWindow::instance()->showMessage(message);
564 }
565
566 void MediaView::removeSelected() {
567     if (!playlistView->selectionModel()->hasSelection()) return;
568     QModelIndexList indexes = playlistView->selectionModel()->selectedIndexes();
569     playlistModel->removeIndexes(indexes);
570 }
571
572 void MediaView::selectVideos(QList<Video*> videos) {
573     foreach (Video *video, videos) {
574         QModelIndex index = playlistModel->indexForVideo(video);
575         playlistView->selectionModel()->select(index, QItemSelectionModel::Select);
576         playlistView->scrollTo(index, QAbstractItemView::EnsureVisible);
577     }
578 }
579
580 void MediaView::selectionChanged(const QItemSelection & /*selected*/, const QItemSelection & /*deselected*/) {
581     const bool gotSelection = playlistView->selectionModel()->hasSelection();
582     The::globalActions()->value("remove")->setEnabled(gotSelection);
583     The::globalActions()->value("moveUp")->setEnabled(gotSelection);
584     The::globalActions()->value("moveDown")->setEnabled(gotSelection);
585 }
586
587 void MediaView::moveUpSelected() {
588     if (!playlistView->selectionModel()->hasSelection()) return;
589
590     QModelIndexList indexes = playlistView->selectionModel()->selectedIndexes();
591     qStableSort(indexes.begin(), indexes.end());
592     playlistModel->move(indexes, true);
593
594     // set current index after row moves to something more intuitive
595     int row = indexes.first().row();
596     playlistView->selectionModel()->setCurrentIndex(playlistModel->index(row>1?row:1), QItemSelectionModel::NoUpdate);
597 }
598
599 void MediaView::moveDownSelected() {
600     if (!playlistView->selectionModel()->hasSelection()) return;
601
602     QModelIndexList indexes = playlistView->selectionModel()->selectedIndexes();
603     qStableSort(indexes.begin(), indexes.end(), qGreater<QModelIndex>());
604     playlistModel->move(indexes, false);
605
606     // set current index after row moves to something more intuitive (respect 1 static item on bottom)
607     int row = indexes.first().row()+1, max = playlistModel->rowCount() - 2;
608     playlistView->selectionModel()->setCurrentIndex(playlistModel->index(row>max?max:row), QItemSelectionModel::NoUpdate);
609 }
610
611 void MediaView::setPlaylistVisible(bool visible) {
612     if (splitter->widget(0)->isVisible() == visible) return;
613     splitter->widget(0)->setVisible(visible);
614     playlistView->setFocus();
615 }
616
617 bool MediaView::isPlaylistVisible() {
618     return splitter->widget(0)->isVisible();
619 }
620
621 void MediaView::saveSplitterState() {
622     QSettings settings;
623     settings.setValue("splitter", splitter->saveState());
624 }
625
626 #ifdef APP_ACTIVATION
627
628 static QPushButton *continueButton;
629
630 void MediaView::demoMessage() {
631     if (mediaObject->state() != Phonon::PlayingState) return;
632     mediaObject->pause();
633
634     QMessageBox msgBox(this);
635     msgBox.setIconPixmap(QPixmap(":/images/app.png").scaled(64, 64, Qt::KeepAspectRatio, Qt::SmoothTransformation));
636     msgBox.setText(tr("This is just the demo version of %1.").arg(Constants::NAME));
637     msgBox.setInformativeText(tr("It allows you to test the application and see if it works for you."));
638     msgBox.setModal(true);
639     // make it a "sheet" on the Mac
640     msgBox.setWindowModality(Qt::WindowModal);
641
642     continueButton = msgBox.addButton("5", QMessageBox::RejectRole);
643     continueButton->setEnabled(false);
644     QPushButton *buyButton = msgBox.addButton(tr("Get the full version"), QMessageBox::ActionRole);
645
646     QTimeLine *timeLine = new QTimeLine(6000, this);
647     timeLine->setCurveShape(QTimeLine::LinearCurve);
648     timeLine->setFrameRange(5, 0);
649     connect(timeLine, SIGNAL(frameChanged(int)), SLOT(updateContinueButton(int)));
650     timeLine->start();
651
652     msgBox.exec();
653
654     if (msgBox.clickedButton() == buyButton) {
655         MainWindow::instance()->showActivationView();
656     } else {
657         mediaObject->play();
658         demoTimer->start(600000);
659     }
660
661     delete timeLine;
662
663 }
664
665 void MediaView::updateContinueButton(int value) {
666     if (value == 0) {
667         continueButton->setText(tr("Continue"));
668         continueButton->setEnabled(true);
669     } else {
670         continueButton->setText(QString::number(value));
671     }
672 }
673
674 #endif
675
676 void MediaView::downloadVideo() {
677     Video* video = playlistModel->activeVideo();
678     if (!video) return;
679     DownloadManager::instance()->addItem(video);
680     The::globalActions()->value("downloads")->setVisible(true);
681     QString message = tr("Downloading %1").arg(video->title());
682     MainWindow::instance()->showMessage(message);
683 }
684
685 void MediaView::snapshot() {
686     QImage image = videoWidget->snapshot();
687     qDebug() << image.size();
688
689     const QPixmap& pixmap = QPixmap::grabWindow(videoWidget->winId());
690     // qDebug() << pixmap.size();
691     videoAreaWidget->showSnapshotPreview(pixmap);
692 }
693
694 void MediaView::fullscreen() {
695     videoAreaWidget->setParent(0);
696     videoAreaWidget->showFullScreen();
697 }
698
699 /*
700 void MediaView::setSlider(QSlider *slider) {
701     this->slider = slider;
702     // slider->setEnabled(false);
703     slider->setTracking(false);
704     // connect(slider, SIGNAL(valueChanged(int)), SLOT(sliderMoved(int)));
705 }
706
707 void MediaView::sliderMoved(int value) {
708     qDebug() << __func__;
709     int sliderPercent = (value * 100) / (slider->maximum() - slider->minimum());
710     qDebug() << slider->minimum() << value << slider->maximum();
711     if (sliderPercent <= downloadItem->currentPercent()) {
712         qDebug() << sliderPercent << downloadItem->currentPercent();
713         mediaObject->seek(value);
714     } else {
715         seekTo(value);
716     }
717 }
718
719 void MediaView::seekTo(int value) {
720     qDebug() << __func__;
721     mediaObject->pause();
722     errorTimer->stop();
723     // mediaObject->clear();
724
725     QString tempDir = QDesktopServices::storageLocation(QDesktopServices::TempLocation);
726     QString tempFile = tempDir + "/minitube" + QString::number(value) + ".mp4";
727     if (!QFile::remove(tempFile)) {
728         qDebug() << "Cannot remove temp file";
729     }
730     Video *videoCopy = downloadItem->getVideo()->clone();
731     QUrl streamUrl = videoCopy->getStreamUrl();
732     streamUrl.addQueryItem("begin", QString::number(value));
733     if (downloadItem) delete downloadItem;
734     downloadItem = new DownloadItem(videoCopy, streamUrl, tempFile, this);
735     connect(downloadItem, SIGNAL(statusChanged()), SLOT(downloadStatusChanged()));
736     // connect(downloadItem, SIGNAL(finished()), SLOT(itemFinished()));
737     downloadItem->start();
738
739     // slider->setMinimum(value);
740
741 }
742
743 */
744
745 void MediaView::findVideoParts() {
746
747     // parts
748     Video* video = playlistModel->activeVideo();
749     if (!video) return;
750
751     QString query = video->title();
752
753     static QString optionalSpace = "\\s*";
754     static QString staticCounterSeparators = "[\\/\\-]";
755     QString counterSeparators = "( of | " +
756             tr("of", "Used in video parts, as in '2 of 3'") +
757             " |" + staticCounterSeparators + ")";
758
759     // numbers from 1 to 15
760     static QString counterNumber = "([1-9]|1[0-5])";
761
762     // query.remove(QRegExp(counterSeparators + optionalSpace + counterNumber));
763     query.remove(QRegExp(counterNumber + optionalSpace + counterSeparators + optionalSpace + counterNumber));
764     query.remove(wordRE("pr?t\\.?" + optionalSpace + counterNumber));
765     query.remove(wordRE("ep\\.?" + optionalSpace + counterNumber));
766     query.remove(wordRE("part" + optionalSpace + counterNumber));
767     query.remove(wordRE("episode" + optionalSpace + counterNumber));
768     query.remove(wordRE(tr("part", "This is for video parts, as in 'Cool video - part 1'") +
769                         optionalSpace + counterNumber));
770     query.remove(wordRE(tr("episode", "This is for video parts, as in 'Cool series - episode 1'") +
771                         optionalSpace + counterNumber));
772     query.remove(QRegExp("[\\(\\)\\[\\]]"));
773
774 #define NUMBERS "one|two|three|four|five|six|seven|eight|nine|ten"
775
776     QRegExp englishNumberRE = QRegExp(QLatin1String(".*(") + NUMBERS + ").*", Qt::CaseInsensitive);
777     // bool numberAsWords = englishNumberRE.exactMatch(query);
778     query.remove(englishNumberRE);
779
780     QRegExp localizedNumberRE = QRegExp(QLatin1String(".*(") + tr(NUMBERS) + ").*", Qt::CaseInsensitive);
781     // if (!numberAsWords) numberAsWords = localizedNumberRE.exactMatch(query);
782     query.remove(localizedNumberRE);
783
784     SearchParams *searchParams = new SearchParams();
785     searchParams->setTransient(true);
786     searchParams->setKeywords(query);
787     searchParams->setAuthor(video->author());
788
789     /*
790     if (!numberAsWords) {
791         qDebug() << "We don't have number as words";
792         // searchParams->setSortBy(SearchParams::SortByNewest);
793         // TODO searchParams->setReverseOrder(true);
794         // TODO searchParams->setMax(50);
795     }
796     */
797
798     search(searchParams);
799
800 }
801
802 void MediaView::relatedVideos() {
803     Video* video = playlistModel->activeVideo();
804     if (!video) return;
805     YTSingleVideoSource *singleVideoSource = new YTSingleVideoSource();
806     singleVideoSource->setVideoId(video->id());
807     setVideoSource(singleVideoSource);
808 }
809
810 void MediaView::shareViaTwitter() {
811     Video* video = playlistModel->activeVideo();
812     if (!video) return;
813     QUrl url("https://twitter.com/intent/tweet");
814     url.addQueryItem("via", "minitubeapp");
815     url.addQueryItem("text", video->title());
816     url.addQueryItem("url", video->webpage().toString());
817     QDesktopServices::openUrl(url);
818 }
819
820 void MediaView::shareViaFacebook() {
821     Video* video = playlistModel->activeVideo();
822     if (!video) return;
823     QUrl url("https://www.facebook.com/sharer.php");
824     url.addQueryItem("t", video->title());
825     url.addQueryItem("u", video->webpage().toString());
826     QDesktopServices::openUrl(url);
827 }
828
829 void MediaView::shareViaBuffer() {
830     Video* video = playlistModel->activeVideo();
831     if (!video) return;
832     QUrl url("http://bufferapp.com/add");
833     url.addQueryItem("via", "minitubeapp");
834     url.addQueryItem("text", video->title());
835     url.addQueryItem("url", video->webpage().toString());
836     url.addQueryItem("picture", video->thumbnailUrl());
837     QDesktopServices::openUrl(url);
838 }
839
840 void MediaView::shareViaEmail() {
841     Video* video = playlistModel->activeVideo();
842     if (!video) return;
843     QUrl url("mailto:");
844     url.addQueryItem("subject", video->title());
845     QString body = video->title() + "\n" +
846             video->webpage().toString() + "\n\n" +
847             tr("Sent from %1").arg(Constants::NAME) + "\n" +
848             Constants::WEBSITE;
849     url.addQueryItem("body", body);
850     QDesktopServices::openUrl(url);
851 }
852
853 void MediaView::authorPushed(QModelIndex index) {
854     Video* video = playlistModel->videoAt(index.row());
855     if (!video) return;
856
857     QString channel = video->authorUri();
858     if (channel.isEmpty()) channel = video->author();
859     if (channel.isEmpty()) return;
860
861     SearchParams *searchParams = new SearchParams();
862     searchParams->setAuthor(channel);
863     searchParams->setSortBy(SearchParams::SortByNewest);
864
865     // go!
866     search(searchParams);
867 }