]> git.sur5r.net Git - minitube/blob - src/mediaview.cpp
Related videos
[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()) currentVideo = downloadItem->getVideo();
293         if (currentVideo && playlistModel->videoAt(row)->webpage() == currentVideo->webpage())
294             return;
295     }
296
297     errorTimer->stop();
298
299     videoAreaWidget->showLoading(video);
300
301     mediaObject->stop();
302     if (downloadItem) {
303         downloadItem->stop();
304         delete downloadItem;
305         downloadItem = 0;
306     }
307
308     connect(video, SIGNAL(gotStreamUrl(QUrl)),
309             SLOT(gotStreamUrl(QUrl)), Qt::UniqueConnection);
310     connect(video, SIGNAL(errorStreamUrl(QString)),
311             SLOT(handleError(QString)), Qt::UniqueConnection);
312
313     video->loadStreamUrl();
314
315     // video title in the statusbar
316     MainWindow::instance()->showMessage(video->title());
317
318     // ensure active item is visible
319     if (row != -1) {
320         QModelIndex index = playlistModel->index(row, 0, QModelIndex());
321         playlistView->scrollTo(index, QAbstractItemView::EnsureVisible);
322     }
323
324     // enable/disable actions
325     The::globalActions()->value("download")->setEnabled(
326                 DownloadManager::instance()->itemForVideo(video) == 0);
327     The::globalActions()->value("skip")->setEnabled(true);
328     The::globalActions()->value("previous")->setEnabled(row > 0);
329     The::globalActions()->value("stopafterthis")->setEnabled(true);
330
331     // see you in gotStreamUrl...
332
333 }
334
335 void MediaView::gotStreamUrl(QUrl streamUrl) {
336     if (reallyStopped) return;
337
338     Video *video = static_cast<Video *>(sender());
339     if (!video) {
340         qDebug() << "Cannot get sender in" << __PRETTY_FUNCTION__;
341         return;
342     }
343     video->disconnect(this);
344
345     QString tempFile = Temporary::filename();
346
347     Video *videoCopy = video->clone();
348     if (downloadItem) {
349         downloadItem->stop();
350         delete downloadItem;
351     }
352     downloadItem = new DownloadItem(videoCopy, streamUrl, tempFile, this);
353     connect(downloadItem, SIGNAL(statusChanged()),
354             SLOT(downloadStatusChanged()), Qt::UniqueConnection);
355     // connect(downloadItem, SIGNAL(progress(int)), SLOT(downloadProgress(int)));
356     connect(downloadItem, SIGNAL(bufferProgress(int)),
357             loadingWidget, SLOT(bufferStatus(int)), Qt::UniqueConnection);
358     // connect(downloadItem, SIGNAL(finished()), SLOT(itemFinished()));
359     connect(video, SIGNAL(errorStreamUrl(QString)),
360             SLOT(handleError(QString)), Qt::UniqueConnection);
361     connect(downloadItem, SIGNAL(error(QString)),
362             SLOT(handleError(QString)), Qt::UniqueConnection);
363     downloadItem->start();
364
365 #ifdef Q_WS_MAC
366     if (mac::canNotify())
367         mac::notify(video->title(), video->author(), video->formattedDuration());
368 #endif
369 }
370
371 /*
372 void MediaView::downloadProgress(int percent) {
373     MainWindow* mainWindow = dynamic_cast<MainWindow*>(window());
374
375     mainWindow->getSeekSlider()->setStyleSheet(" QSlider::groove:horizontal {"
376         "border: 1px solid #999999;"
377         // "border-left: 50px solid rgba(255, 0, 0, 128);"
378         "height: 8px;"
379         "background: qlineargradient(x1:0, y1:0, x2:.5, y2:0, stop:0 rgba(255, 0, 0, 92), stop:"
380         + QString::number(percent/100.0) +
381
382         " rgba(255, 0, 0, 92), stop:" + QString::number((percent+1)/100.0) + " transparent, stop:1 transparent);"
383         "margin: 2px 0;"
384     "}"
385     "QSlider::handle:horizontal {"
386         "background: qlineargradient(x1:0, y1:0, x2:1, y2:1, stop:0 #b4b4b4, stop:1 #8f8f8f);"
387         "border: 1px solid #5c5c5c;"
388         "width: 16px;"
389         "height: 16px;"
390         "margin: -2px 0;"
391         "border-radius: 8px;"
392     "}"
393
394     );
395 }
396
397 */
398
399 void MediaView::downloadStatusChanged() {
400     switch(downloadItem->status()) {
401     case Downloading:
402         startPlaying();
403         break;
404     case Starting:
405         // qDebug() << "Starting";
406         break;
407     case Finished:
408         // qDebug() << "Finished" << mediaObject->state();
409         // if (mediaObject->state() == Phonon::StoppedState) startPlaying();
410 #ifdef Q_WS_X11
411         MainWindow::instance()->getSeekSlider()->setEnabled(mediaObject->isSeekable());
412 #endif
413         break;
414     case Failed:
415         // qDebug() << "Failed";
416     case Idle:
417         // qDebug() << "Idle";
418         break;
419     }
420 }
421
422 void MediaView::startPlaying() {
423     if (reallyStopped) return;
424     if (!downloadItem) {
425         skip();
426         return;
427     }
428
429     // go!
430     QString source = downloadItem->currentFilename();
431     qDebug() << "Playing" << source;
432     mediaObject->setCurrentSource(source);
433     mediaObject->play();
434 #ifdef Q_WS_X11
435     MainWindow::instance()->getSeekSlider()->setEnabled(false);
436 #endif
437
438     // ensure we always have 10 videos ahead
439     playlistModel->searchNeeded();
440
441     // ensure active item is visible
442     int row = playlistModel->activeRow();
443     if (row != -1) {
444         QModelIndex index = playlistModel->index(row, 0, QModelIndex());
445         playlistView->scrollTo(index, QAbstractItemView::EnsureVisible);
446     }
447
448 #ifdef APP_ACTIVATION
449     if (!Activation::instance().isActivated())
450         demoTimer->start(180000);
451 #endif
452
453 }
454
455 void MediaView::itemActivated(const QModelIndex &index) {
456     if (playlistModel->rowExists(index.row())) {
457
458         // if it's the current video, just rewind and play
459         Video *activeVideo = playlistModel->activeVideo();
460         Video *video = playlistModel->videoAt(index.row());
461         if (activeVideo && video && activeVideo == video) {
462             mediaObject->seek(0);
463             mediaObject->play();
464         } else playlistModel->setActiveRow(index.row());
465
466         // the user doubleclicked on the "Search More" item
467     } else {
468         playlistModel->searchMore();
469         playlistView->selectionModel()->clearSelection();
470     }
471 }
472
473 void MediaView::currentSourceChanged(const Phonon::MediaSource /* source */ ) {
474
475 }
476
477 void MediaView::skipVideo() {
478     // skippedVideo is useful for DELAYED skip operations
479     // in order to be sure that we're skipping the video we wanted
480     // and not another one
481     if (skippedVideo) {
482         if (playlistModel->activeVideo() != skippedVideo) {
483             qDebug() << "Skip of video canceled";
484             return;
485         }
486         int nextRow = playlistModel->rowForVideo(skippedVideo);
487         nextRow++;
488         if (nextRow == -1) return;
489         playlistModel->setActiveRow(nextRow);
490     }
491 }
492
493 void MediaView::skip() {
494     int nextRow = playlistModel->nextRow();
495     if (nextRow == -1) return;
496     playlistModel->setActiveRow(nextRow);
497 }
498
499 void MediaView::skipBackward() {
500     int prevRow = playlistModel->previousRow();
501     if (prevRow == -1) return;
502     playlistModel->setActiveRow(prevRow);
503 }
504
505 void MediaView::aboutToFinish() {
506     qint64 currentTime = mediaObject->currentTime();
507     qDebug() << __PRETTY_FUNCTION__ << currentTime;
508     if (currentTime + 10000 < mediaObject->totalTime()) {
509         // mediaObject->seek(mediaObject->currentTime());
510         // QTimer::singleShot(500, this, SLOT(playbackResume()));
511         mediaObject->seek(currentTime);
512         mediaObject->play();
513     }
514 }
515
516 void MediaView::playbackFinished() {
517     qDebug() << __PRETTY_FUNCTION__ << mediaObject->currentTime();
518     // qDebug() << "finished" << mediaObject->currentTime() << mediaObject->totalTime();
519     // add 10 secs for imprecise Phonon backends (VLC, Xine)
520     if (mediaObject->currentTime() + 10000 < mediaObject->totalTime()) {
521         // mediaObject->seek(mediaObject->currentTime());
522         QTimer::singleShot(500, this, SLOT(playbackResume()));
523     } else {
524         QAction* stopAfterThisAction = The::globalActions()->value("stopafterthis");
525         if (stopAfterThisAction->isChecked()) {
526             stopAfterThisAction->setChecked(false);
527         } else skip();
528     }
529 }
530
531 void MediaView::playbackResume() {
532     qDebug() << __PRETTY_FUNCTION__ << mediaObject->currentTime();
533     mediaObject->seek(mediaObject->currentTime());
534     mediaObject->play();
535 }
536
537 void MediaView::openWebPage() {
538     Video* video = playlistModel->activeVideo();
539     if (!video) return;
540     mediaObject->pause();
541     QDesktopServices::openUrl(video->webpage());
542 }
543
544 void MediaView::copyWebPage() {
545     Video* video = playlistModel->activeVideo();
546     if (!video) return;
547     QString address = video->webpage().toString();
548     QApplication::clipboard()->setText(address);
549     QString message = tr("You can now paste the YouTube link into another application");
550     MainWindow::instance()->showMessage(message);
551 }
552
553 void MediaView::copyVideoLink() {
554     Video* video = playlistModel->activeVideo();
555     if (!video) return;
556     QApplication::clipboard()->setText(video->getStreamUrl().toEncoded());
557     QString message = tr("You can now paste the video stream URL into another application")
558             + ". " + tr("The link will be valid only for a limited time.");
559     MainWindow::instance()->showMessage(message);
560 }
561
562 void MediaView::removeSelected() {
563     if (!playlistView->selectionModel()->hasSelection()) return;
564     QModelIndexList indexes = playlistView->selectionModel()->selectedIndexes();
565     playlistModel->removeIndexes(indexes);
566 }
567
568 void MediaView::selectVideos(QList<Video*> videos) {
569     foreach (Video *video, videos) {
570         QModelIndex index = playlistModel->indexForVideo(video);
571         playlistView->selectionModel()->select(index, QItemSelectionModel::Select);
572         playlistView->scrollTo(index, QAbstractItemView::EnsureVisible);
573     }
574 }
575
576 void MediaView::selectionChanged(const QItemSelection & /*selected*/, const QItemSelection & /*deselected*/) {
577     const bool gotSelection = playlistView->selectionModel()->hasSelection();
578     The::globalActions()->value("remove")->setEnabled(gotSelection);
579     The::globalActions()->value("moveUp")->setEnabled(gotSelection);
580     The::globalActions()->value("moveDown")->setEnabled(gotSelection);
581 }
582
583 void MediaView::moveUpSelected() {
584     if (!playlistView->selectionModel()->hasSelection()) return;
585
586     QModelIndexList indexes = playlistView->selectionModel()->selectedIndexes();
587     qStableSort(indexes.begin(), indexes.end());
588     playlistModel->move(indexes, true);
589
590     // set current index after row moves to something more intuitive
591     int row = indexes.first().row();
592     playlistView->selectionModel()->setCurrentIndex(playlistModel->index(row>1?row:1), QItemSelectionModel::NoUpdate);
593 }
594
595 void MediaView::moveDownSelected() {
596     if (!playlistView->selectionModel()->hasSelection()) return;
597
598     QModelIndexList indexes = playlistView->selectionModel()->selectedIndexes();
599     qStableSort(indexes.begin(), indexes.end(), qGreater<QModelIndex>());
600     playlistModel->move(indexes, false);
601
602     // set current index after row moves to something more intuitive (respect 1 static item on bottom)
603     int row = indexes.first().row()+1, max = playlistModel->rowCount() - 2;
604     playlistView->selectionModel()->setCurrentIndex(playlistModel->index(row>max?max:row), QItemSelectionModel::NoUpdate);
605 }
606
607 void MediaView::setPlaylistVisible(bool visible) {
608     if (splitter->widget(0)->isVisible() == visible) return;
609     splitter->widget(0)->setVisible(visible);
610     playlistView->setFocus();
611 }
612
613 bool MediaView::isPlaylistVisible() {
614     return splitter->widget(0)->isVisible();
615 }
616
617 void MediaView::saveSplitterState() {
618     QSettings settings;
619     settings.setValue("splitter", splitter->saveState());
620 }
621
622 #ifdef APP_ACTIVATION
623
624 static QPushButton *continueButton;
625
626 void MediaView::demoMessage() {
627     if (mediaObject->state() != Phonon::PlayingState) return;
628     mediaObject->pause();
629
630     QMessageBox msgBox(this);
631     msgBox.setIconPixmap(QPixmap(":/images/app.png").scaled(64, 64, Qt::KeepAspectRatio, Qt::SmoothTransformation));
632     msgBox.setText(tr("This is just the demo version of %1.").arg(Constants::NAME));
633     msgBox.setInformativeText(tr("It allows you to test the application and see if it works for you."));
634     msgBox.setModal(true);
635     // make it a "sheet" on the Mac
636     msgBox.setWindowModality(Qt::WindowModal);
637
638     continueButton = msgBox.addButton("5", QMessageBox::RejectRole);
639     continueButton->setEnabled(false);
640     QPushButton *buyButton = msgBox.addButton(tr("Get the full version"), QMessageBox::ActionRole);
641
642     QTimeLine *timeLine = new QTimeLine(6000, this);
643     timeLine->setCurveShape(QTimeLine::LinearCurve);
644     timeLine->setFrameRange(5, 0);
645     connect(timeLine, SIGNAL(frameChanged(int)), SLOT(updateContinueButton(int)));
646     timeLine->start();
647
648     msgBox.exec();
649
650     if (msgBox.clickedButton() == buyButton) {
651         MainWindow::instance()->showActivationView();
652     } else {
653         mediaObject->play();
654         demoTimer->start(600000);
655     }
656
657     delete timeLine;
658
659 }
660
661 void MediaView::updateContinueButton(int value) {
662     if (value == 0) {
663         continueButton->setText(tr("Continue"));
664         continueButton->setEnabled(true);
665     } else {
666         continueButton->setText(QString::number(value));
667     }
668 }
669
670 #endif
671
672 void MediaView::downloadVideo() {
673     Video* video = playlistModel->activeVideo();
674     if (!video) return;
675     DownloadManager::instance()->addItem(video);
676     The::globalActions()->value("downloads")->setVisible(true);
677     QString message = tr("Downloading %1").arg(video->title());
678     MainWindow::instance()->showMessage(message);
679 }
680
681 void MediaView::snapshot() {
682     QImage image = videoWidget->snapshot();
683     qDebug() << image.size();
684
685     const QPixmap& pixmap = QPixmap::grabWindow(videoWidget->winId());
686     // qDebug() << pixmap.size();
687     videoAreaWidget->showSnapshotPreview(pixmap);
688 }
689
690 void MediaView::fullscreen() {
691     videoAreaWidget->setParent(0);
692     videoAreaWidget->showFullScreen();
693 }
694
695 /*
696 void MediaView::setSlider(QSlider *slider) {
697     this->slider = slider;
698     // slider->setEnabled(false);
699     slider->setTracking(false);
700     // connect(slider, SIGNAL(valueChanged(int)), SLOT(sliderMoved(int)));
701 }
702
703 void MediaView::sliderMoved(int value) {
704     qDebug() << __func__;
705     int sliderPercent = (value * 100) / (slider->maximum() - slider->minimum());
706     qDebug() << slider->minimum() << value << slider->maximum();
707     if (sliderPercent <= downloadItem->currentPercent()) {
708         qDebug() << sliderPercent << downloadItem->currentPercent();
709         mediaObject->seek(value);
710     } else {
711         seekTo(value);
712     }
713 }
714
715 void MediaView::seekTo(int value) {
716     qDebug() << __func__;
717     mediaObject->pause();
718     errorTimer->stop();
719     // mediaObject->clear();
720
721     QString tempDir = QDesktopServices::storageLocation(QDesktopServices::TempLocation);
722     QString tempFile = tempDir + "/minitube" + QString::number(value) + ".mp4";
723     if (!QFile::remove(tempFile)) {
724         qDebug() << "Cannot remove temp file";
725     }
726     Video *videoCopy = downloadItem->getVideo()->clone();
727     QUrl streamUrl = videoCopy->getStreamUrl();
728     streamUrl.addQueryItem("begin", QString::number(value));
729     if (downloadItem) delete downloadItem;
730     downloadItem = new DownloadItem(videoCopy, streamUrl, tempFile, this);
731     connect(downloadItem, SIGNAL(statusChanged()), SLOT(downloadStatusChanged()));
732     // connect(downloadItem, SIGNAL(finished()), SLOT(itemFinished()));
733     downloadItem->start();
734
735     // slider->setMinimum(value);
736
737 }
738
739 */
740
741 void MediaView::findVideoParts() {
742
743     // parts
744     Video* video = playlistModel->activeVideo();
745     if (!video) return;
746
747     QString query = video->title();
748
749     static QString optionalSpace = "\\s*";
750     static QString staticCounterSeparators = "[\\/\\-]";
751     QString counterSeparators = "( of | " +
752             tr("of", "Used in video parts, as in '2 of 3'") +
753             " |" + staticCounterSeparators + ")";
754
755     // numbers from 1 to 15
756     static QString counterNumber = "([1-9]|1[0-5])";
757
758     // query.remove(QRegExp(counterSeparators + optionalSpace + counterNumber));
759     query.remove(QRegExp(counterNumber + optionalSpace + counterSeparators + optionalSpace + counterNumber));
760     query.remove(wordRE("pr?t\\.?" + optionalSpace + counterNumber));
761     query.remove(wordRE("ep\\.?" + optionalSpace + counterNumber));
762     query.remove(wordRE("part" + optionalSpace + counterNumber));
763     query.remove(wordRE("episode" + optionalSpace + counterNumber));
764     query.remove(wordRE(tr("part", "This is for video parts, as in 'Cool video - part 1'") +
765                         optionalSpace + counterNumber));
766     query.remove(wordRE(tr("episode", "This is for video parts, as in 'Cool series - episode 1'") +
767                         optionalSpace + counterNumber));
768     query.remove(QRegExp("[\\(\\)\\[\\]]"));
769
770 #define NUMBERS "one|two|three|four|five|six|seven|eight|nine|ten"
771
772     QRegExp englishNumberRE = QRegExp(QLatin1String(".*(") + NUMBERS + ").*", Qt::CaseInsensitive);
773     // bool numberAsWords = englishNumberRE.exactMatch(query);
774     query.remove(englishNumberRE);
775
776     QRegExp localizedNumberRE = QRegExp(QLatin1String(".*(") + tr(NUMBERS) + ").*", Qt::CaseInsensitive);
777     // if (!numberAsWords) numberAsWords = localizedNumberRE.exactMatch(query);
778     query.remove(localizedNumberRE);
779
780     SearchParams *searchParams = new SearchParams();
781     searchParams->setTransient(true);
782     searchParams->setKeywords(query);
783     searchParams->setAuthor(video->author());
784
785     /*
786     if (!numberAsWords) {
787         qDebug() << "We don't have number as words";
788         // searchParams->setSortBy(SearchParams::SortByNewest);
789         // TODO searchParams->setReverseOrder(true);
790         // TODO searchParams->setMax(50);
791     }
792     */
793
794     search(searchParams);
795
796 }
797
798 void MediaView::relatedVideos() {
799     Video* video = playlistModel->activeVideo();
800     if (!video) return;
801     YTSingleVideoSource *singleVideoSource = new YTSingleVideoSource();
802     singleVideoSource->setVideoId(video->id());
803     setVideoSource(singleVideoSource);
804 }
805
806 void MediaView::shareViaTwitter() {
807     Video* video = playlistModel->activeVideo();
808     if (!video) return;
809     QUrl url("https://twitter.com/intent/tweet");
810     url.addQueryItem("via", "minitubeapp");
811     url.addQueryItem("text", video->title());
812     url.addQueryItem("url", video->webpage().toString());
813     QDesktopServices::openUrl(url);
814 }
815
816 void MediaView::shareViaFacebook() {
817     Video* video = playlistModel->activeVideo();
818     if (!video) return;
819     QUrl url("https://www.facebook.com/sharer.php");
820     url.addQueryItem("t", video->title());
821     url.addQueryItem("u", video->webpage().toString());
822     QDesktopServices::openUrl(url);
823 }
824
825 void MediaView::shareViaBuffer() {
826     Video* video = playlistModel->activeVideo();
827     if (!video) return;
828     QUrl url("http://bufferapp.com/add");
829     url.addQueryItem("via", "minitubeapp");
830     url.addQueryItem("text", video->title());
831     url.addQueryItem("url", video->webpage().toString());
832     url.addQueryItem("picture", video->thumbnailUrl());
833     QDesktopServices::openUrl(url);
834 }
835
836 void MediaView::shareViaEmail() {
837     Video* video = playlistModel->activeVideo();
838     if (!video) return;
839     QUrl url("mailto:");
840     url.addQueryItem("subject", video->title());
841     QString body = video->title() + "\n" +
842             video->webpage().toString() + "\n\n" +
843             tr("Sent from %1").arg(Constants::NAME) + "\n" +
844             Constants::WEBSITE;
845     url.addQueryItem("body", body);
846     QDesktopServices::openUrl(url);
847 }
848
849 void MediaView::authorPushed(QModelIndex index) {
850     Video* video = playlistModel->videoAt(index.row());
851     if (!video) return;
852
853     QString channel = video->authorUri();
854     if (channel.isEmpty()) channel = video->author();
855     if (channel.isEmpty()) return;
856
857     SearchParams *searchParams = new SearchParams();
858     searchParams->setAuthor(channel);
859     searchParams->setSortBy(SearchParams::SortByNewest);
860
861     // go!
862     search(searchParams);
863 }