]> git.sur5r.net Git - minitube/blob - src/MediaView.cpp
7cda1cb58ed1bf98f393b02a0ec6acd9c9eb4c9e
[minitube] / src / MediaView.cpp
1 #include "MediaView.h"
2 #include "playlist/PrettyItemDelegate.h"
3 #include "networkaccess.h"
4 #include "videowidget.h"
5 #include "minisplitter.h"
6 #include "constants.h"
7 #include "downloadmanager.h"
8
9 namespace The {
10     QMap<QString, QAction*>* globalActions();
11     QMap<QString, QMenu*>* globalMenus();
12     QNetworkAccessManager* networkAccessManager();
13 }
14
15 MediaView::MediaView(QWidget *parent) : QWidget(parent) {
16
17 #ifdef APP_DEMO
18     tracksPlayed = 0;
19 #endif
20
21     reallyStopped = false;
22
23     QBoxLayout *layout = new QHBoxLayout();
24     layout->setMargin(0);
25
26     splitter = new MiniSplitter(this);
27     splitter->setChildrenCollapsible(false);
28
29     sortBar = new THBlackBar(this);
30     mostRelevantAction = new QAction(tr("Most relevant"), this);
31     QKeySequence keySequence(Qt::CTRL + Qt::Key_1);
32     mostRelevantAction->setShortcut(keySequence);
33     mostRelevantAction->setStatusTip(mostRelevantAction->text() + " (" + keySequence.toString(QKeySequence::NativeText) + ")");
34     addAction(mostRelevantAction);
35     connect(mostRelevantAction, SIGNAL(triggered()), this, SLOT(searchMostRelevant()), Qt::QueuedConnection);
36     sortBar->addAction(mostRelevantAction);
37     mostRecentAction = new QAction(tr("Most recent"), this);
38     keySequence = QKeySequence(Qt::CTRL + Qt::Key_2);
39     mostRecentAction->setShortcut(keySequence);
40     mostRecentAction->setStatusTip(mostRecentAction->text() + " (" + keySequence.toString(QKeySequence::NativeText) + ")");
41     addAction(mostRecentAction);
42     connect(mostRecentAction, SIGNAL(triggered()), this, SLOT(searchMostRecent()), Qt::QueuedConnection);
43     sortBar->addAction(mostRecentAction);
44     mostViewedAction = new QAction(tr("Most viewed"), this);
45     keySequence = QKeySequence(Qt::CTRL + Qt::Key_3);
46     mostViewedAction->setShortcut(keySequence);
47     mostViewedAction->setStatusTip(mostViewedAction->text() + " (" + keySequence.toString(QKeySequence::NativeText) + ")");
48     addAction(mostViewedAction);
49     connect(mostViewedAction, SIGNAL(triggered()), this, SLOT(searchMostViewed()), Qt::QueuedConnection);
50     sortBar->addAction(mostViewedAction);
51
52     listView = new QListView(this);
53     listView->setItemDelegate(new PrettyItemDelegate(this));
54     listView->setSelectionMode(QAbstractItemView::ExtendedSelection);
55
56     // dragndrop
57     listView->setDragEnabled(true);
58     listView->setAcceptDrops(true);
59     listView->setDropIndicatorShown(true);
60     listView->setDragDropMode(QAbstractItemView::DragDrop);
61
62     // cosmetics
63     listView->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
64     listView->setFrameShape( QFrame::NoFrame );
65     listView->setAttribute(Qt::WA_MacShowFocusRect, false);
66     listView->setMinimumSize(320,240);
67     listView->setUniformItemSizes(true);
68
69     // respond to the user doubleclicking a playlist item
70     connect(listView, SIGNAL(activated(const QModelIndex &)), this, SLOT(itemActivated(const QModelIndex &)));
71
72     listModel = new ListModel(this);
73     connect(listModel, SIGNAL(activeRowChanged(int)), this, SLOT(activeRowChanged(int)));
74     // needed to restore the selection after dragndrop
75     connect(listModel, SIGNAL(needSelectionFor(QList<Video*>)), this, SLOT(selectVideos(QList<Video*>)));
76     listView->setModel(listModel);
77
78     connect(listView->selectionModel(),
79             SIGNAL(selectionChanged ( const QItemSelection & , const QItemSelection & )),
80             this, SLOT(selectionChanged ( const QItemSelection & , const QItemSelection & )));
81
82     playlistWidget = new PlaylistWidget(this, sortBar, listView);
83
84     splitter->addWidget(playlistWidget);
85
86     videoAreaWidget = new VideoAreaWidget(this);
87     videoAreaWidget->setMinimumSize(320,240);
88
89 #ifdef APP_MAC
90     // mouse autohide does not work on the Mac (no mouseMoveEvent)
91     videoWidget = new Phonon::VideoWidget(this);
92 #else
93     videoWidget = new VideoWidget(this);
94 #endif
95
96     videoAreaWidget->setVideoWidget(videoWidget);
97     videoAreaWidget->setListModel(listModel);
98
99     loadingWidget = new LoadingWidget(this);
100     videoAreaWidget->setLoadingWidget(loadingWidget);
101
102     splitter->addWidget(videoAreaWidget);
103
104     layout->addWidget(splitter);
105     setLayout(layout);
106
107     // restore splitter state
108     QSettings settings;
109     splitter->restoreState(settings.value("splitter").toByteArray());
110
111     errorTimer = new QTimer(this);
112     errorTimer->setSingleShot(true);
113     errorTimer->setInterval(3000);
114     connect(errorTimer, SIGNAL(timeout()), SLOT(skipVideo()));
115
116     workaroundTimer = new QTimer(this);
117     workaroundTimer->setSingleShot(true);
118     workaroundTimer->setInterval(3000);
119     connect(workaroundTimer, SIGNAL(timeout()), SLOT(timerPlay()));
120
121 }
122
123 void MediaView::initialize() {
124     connect(videoAreaWidget, SIGNAL(doubleClicked()), The::globalActions()->value("fullscreen"), SLOT(trigger()));
125     videoAreaWidget->setContextMenuPolicy(Qt::CustomContextMenu);
126     connect(videoAreaWidget, SIGNAL(customContextMenuRequested(QPoint)),
127             this, SLOT(showVideoContextMenu(QPoint)));
128 }
129
130 void MediaView::setMediaObject(Phonon::MediaObject *mediaObject) {
131     this->mediaObject = mediaObject;
132     Phonon::createPath(this->mediaObject, videoWidget);
133     connect(mediaObject, SIGNAL(finished()), this, SLOT(skip()));
134     connect(mediaObject, SIGNAL(stateChanged(Phonon::State, Phonon::State)),
135             this, SLOT(stateChanged(Phonon::State, Phonon::State)));
136     connect(mediaObject, SIGNAL(currentSourceChanged(Phonon::MediaSource)),
137             this, SLOT(currentSourceChanged(Phonon::MediaSource)));
138     connect(mediaObject, SIGNAL(bufferStatus(int)), loadingWidget, SLOT(bufferStatus(int)));
139 }
140
141 void MediaView::search(SearchParams *searchParams) {
142     reallyStopped = false;
143
144 #ifdef APP_DEMO
145     tracksPlayed = 0;
146 #endif
147
148     videoAreaWidget->clear();
149     workaroundTimer->stop();
150     errorTimer->stop();
151
152     this->searchParams = searchParams;
153
154     // start serching for videos
155     listModel->search(searchParams);
156
157     // this implies that the enum and the bar action order is the same
158     sortBar->setCheckedAction(searchParams->sortBy()-1);
159
160     listView->setFocus();
161
162 }
163
164 void MediaView::disappear() {
165     timerPlayFlag = true;
166 }
167
168 void MediaView::handleError(QString message) {
169     videoAreaWidget->showError(message);
170     skippedVideo = listModel->activeVideo();
171     // recover from errors by skipping to the next video
172     errorTimer->start(2000);
173 }
174
175 void MediaView::stateChanged(Phonon::State newState, Phonon::State /*oldState*/)
176 {
177
178     // qDebug() << "Phonon state: " << newState << oldState;
179
180     switch (newState) {
181
182     case Phonon::ErrorState:
183         qDebug() << "Phonon error:" << mediaObject->errorString() << mediaObject->errorType();
184         handleError(mediaObject->errorString());
185         break;
186
187     case Phonon::PlayingState:
188         //qDebug("playing");
189         videoAreaWidget->showVideo();
190         break;
191
192     case Phonon::StoppedState:
193         //qDebug("stopped");
194         // play() has already been called when setting the source
195         // but Phonon on Linux needs a little more help to start playback
196         if (!reallyStopped) mediaObject->play();
197
198 #ifdef APP_MAC
199         // Workaround for Mac playback start problem
200         if (!timerPlayFlag) {
201             workaroundTimer->start();
202         }
203 #endif
204
205         break;
206
207          case Phonon::PausedState:
208         //qDebug("paused");
209         break;
210
211          case Phonon::BufferingState:
212         //qDebug("buffering");
213         break;
214
215          case Phonon::LoadingState:
216         //qDebug("loading");
217         break;
218
219          default:
220         ;
221     }
222 }
223
224 void MediaView::pause() {
225     // qDebug() << "pause() called" << mediaObject->state();
226     switch( mediaObject->state() ) {
227     case Phonon::PlayingState:
228         mediaObject->pause();
229         break;
230     default:
231         mediaObject->play();
232         break;
233     }
234 }
235
236 void MediaView::stop() {
237     listModel->abortSearch();
238     reallyStopped = true;
239     mediaObject->stop();
240     videoAreaWidget->clear();
241     workaroundTimer->stop();
242     errorTimer->stop();
243     listView->selectionModel()->clearSelection();
244 }
245
246 void MediaView::activeRowChanged(int row) {
247     if (reallyStopped) return;
248
249     Video *video = listModel->videoAt(row);
250     if (!video) return;
251
252     // now that we have a new video to play
253     // stop all the timers
254     workaroundTimer->stop();
255     errorTimer->stop();
256
257     // immediately show the loading widget
258     videoAreaWidget->showLoading(video);
259
260     connect(video, SIGNAL(gotStreamUrl(QUrl)), SLOT(gotStreamUrl(QUrl)));
261     // TODO handle signal in a proper slot and impl item error status
262     connect(video, SIGNAL(errorStreamUrl(QString)), SLOT(handleError(QString)));
263
264     video->loadStreamUrl();
265
266     // reset the timer flag
267     timerPlayFlag = false;
268
269     // video title in the statusbar
270     QMainWindow* mainWindow = dynamic_cast<QMainWindow*>(window());
271     if (mainWindow) mainWindow->statusBar()->showMessage(video->title());
272
273     The::globalActions()->value("download")->setEnabled(DownloadManager::instance()->itemForVideo(video) == 0);
274
275     // see you in gotStreamUrl...
276
277 }
278
279 void MediaView::gotStreamUrl(QUrl streamUrl) {
280     if (reallyStopped) return;
281
282     Video *video = static_cast<Video *>(sender());
283     if (!video) {
284         qDebug() << "Cannot get sender";
285         return;
286     }
287     video->disconnect(this);
288
289     // go!
290     qDebug() << "Playing" << streamUrl.toString();
291     mediaObject->setCurrentSource(streamUrl);
292     mediaObject->play();
293
294     // ensure we always have 10 videos ahead
295     listModel->searchNeeded();
296
297     // ensure active item is visible
298     int row = listModel->activeRow();
299     if (row != -1) {
300         QModelIndex index = listModel->index(row, 0, QModelIndex());
301         listView->scrollTo(index, QAbstractItemView::EnsureVisible);
302     }
303
304 #ifdef APP_DEMO
305     if (tracksPlayed > 1) demoExpired();
306     else tracksPlayed++;
307 #endif
308
309 }
310
311 void MediaView::itemActivated(const QModelIndex &index) {
312     if (listModel->rowExists(index.row()))
313         listModel->setActiveRow(index.row());
314     // the user doubleclicked on the "Search More" item
315     else listModel->searchMore();
316 }
317
318 void MediaView::currentSourceChanged(const Phonon::MediaSource /* source */ ) {
319
320 }
321
322 void MediaView::skipVideo() {
323     // skippedVideo is useful for DELAYED skip operations
324     // in order to be sure that we're skipping the video we wanted
325     // and not another one
326     if (skippedVideo) {
327         if (listModel->activeVideo() != skippedVideo) {
328             qDebug() << "Skip of video canceled";
329             return;
330         }
331         int nextRow = listModel->rowForVideo(skippedVideo);
332         nextRow++;
333         if (nextRow == -1) return;
334         listModel->setActiveRow(nextRow);
335     }
336 }
337
338 void MediaView::skip() {
339     int nextRow = listModel->nextRow();
340     if (nextRow == -1) return;
341     listModel->setActiveRow(nextRow);
342 }
343
344 void MediaView::openWebPage() {
345     Video* video = listModel->activeVideo();
346     if (!video) return;
347     mediaObject->pause();
348     QDesktopServices::openUrl(video->webpage());
349 }
350
351 void MediaView::copyWebPage() {
352     Video* video = listModel->activeVideo();
353     if (!video) return;
354     QString address = video->webpage().toString();
355     address.remove("&feature=youtube_gdata");
356     QApplication::clipboard()->setText(address);
357     QMainWindow* mainWindow = dynamic_cast<QMainWindow*>(window());
358     QString message = tr("You can now paste the YouTube link into another application");
359     if (mainWindow) mainWindow->statusBar()->showMessage(message);
360 }
361
362 void MediaView::copyVideoLink() {
363     Video* video = listModel->activeVideo();
364     if (!video) return;
365     QApplication::clipboard()->setText(video->getStreamUrl().toString());
366     QString message = tr("You can now paste the video stream URL into another application")
367                       + ". " + tr("The link will be valid only for a limited time.");
368     QMainWindow* mainWindow = dynamic_cast<QMainWindow*>(window());
369     if (mainWindow) mainWindow->statusBar()->showMessage(message);
370 }
371
372 void MediaView::removeSelected() {
373     if (!listView->selectionModel()->hasSelection()) return;
374     QModelIndexList indexes = listView->selectionModel()->selectedIndexes();
375     listModel->removeIndexes(indexes);
376 }
377
378 void MediaView::selectVideos(QList<Video*> videos) {
379     foreach (Video *video, videos) {
380         QModelIndex index = listModel->indexForVideo(video);
381         listView->selectionModel()->select(index, QItemSelectionModel::Select);
382         listView->scrollTo(index, QAbstractItemView::EnsureVisible);
383     }
384 }
385
386 void MediaView::selectionChanged(const QItemSelection & /*selected*/, const QItemSelection & /*deselected*/) {
387     const bool gotSelection = listView->selectionModel()->hasSelection();
388     The::globalActions()->value("remove")->setEnabled(gotSelection);
389     The::globalActions()->value("moveUp")->setEnabled(gotSelection);
390     The::globalActions()->value("moveDown")->setEnabled(gotSelection);
391 }
392
393 void MediaView::moveUpSelected() {
394     if (!listView->selectionModel()->hasSelection()) return;
395
396     QModelIndexList indexes = listView->selectionModel()->selectedIndexes();
397     qStableSort(indexes.begin(), indexes.end());
398     listModel->move(indexes, true);
399
400     // set current index after row moves to something more intuitive
401     int row = indexes.first().row();
402     listView->selectionModel()->setCurrentIndex(listModel->index(row>1?row:1), QItemSelectionModel::NoUpdate);
403 }
404
405 void MediaView::moveDownSelected() {
406     if (!listView->selectionModel()->hasSelection()) return;
407
408     QModelIndexList indexes = listView->selectionModel()->selectedIndexes();
409     qStableSort(indexes.begin(), indexes.end(), qGreater<QModelIndex>());
410     listModel->move(indexes, false);
411
412     // set current index after row moves to something more intuitive (respect 1 static item on bottom)
413     int row = indexes.first().row()+1, max = listModel->rowCount() - 2;
414     listView->selectionModel()->setCurrentIndex(listModel->index(row>max?max:row), QItemSelectionModel::NoUpdate);
415 }
416
417 void MediaView::showVideoContextMenu(QPoint point) {
418     The::globalMenus()->value("video")->popup(videoWidget->mapToGlobal(point));
419 }
420
421 void MediaView::searchMostRelevant() {
422     searchParams->setSortBy(SearchParams::SortByRelevance);
423     search(searchParams);
424 }
425
426 void MediaView::searchMostRecent() {
427     searchParams->setSortBy(SearchParams::SortByNewest);
428     search(searchParams);
429 }
430
431 void MediaView::searchMostViewed() {
432     searchParams->setSortBy(SearchParams::SortByViewCount);
433     search(searchParams);
434 }
435
436 void MediaView::setPlaylistVisible(bool visible) {
437     playlistWidget->setVisible(visible);
438 }
439
440 void MediaView::timerPlay() {
441     // Workaround Phonon bug on Mac OSX
442     // qDebug() << mediaObject->currentTime();
443     if (mediaObject->currentTime() <= 0 && mediaObject->state() == Phonon::PlayingState) {
444         // qDebug() << "Mac playback workaround";
445         mediaObject->pause();
446         // QTimer::singleShot(1000, mediaObject, SLOT(play()));
447         mediaObject->play();
448     }
449 }
450
451 void MediaView::saveSplitterState() {
452     QSettings settings;
453     settings.setValue("splitter", splitter->saveState());
454 }
455
456 #ifdef APP_DEMO
457 void MediaView::demoExpired() {
458     mediaObject->pause();
459
460     QMessageBox msgBox;
461     msgBox.setIconPixmap(QPixmap(":/images/app.png").scaled(64, 64, Qt::KeepAspectRatio, Qt::SmoothTransformation));
462     msgBox.setText(tr("This is just the demo version of %1.").arg(Constants::APP_NAME) + " " +
463                    tr("It allows you to test the application and see if it works for you.")
464                    );
465     msgBox.setModal(true);
466
467     QPushButton *quitButton = msgBox.addButton(tr("Continue"), QMessageBox::RejectRole);
468     QPushButton *buyButton = msgBox.addButton(tr("Get the full version"), QMessageBox::ActionRole);
469
470     msgBox.exec();
471
472     if (msgBox.clickedButton() == buyButton) {
473         QDesktopServices::openUrl(QString(Constants::WEBSITE) + "#download");
474     } else {
475         mediaObject->play();
476     }
477
478     tracksPlayed = 1;
479 }
480 #endif
481
482 void MediaView::downloadVideo() {
483     Video* video = listModel->activeVideo();
484     if (!video) return;
485
486     DownloadManager::instance()->addItem(video);
487
488     // TODO animate
489
490     The::globalActions()->value("downloads")->setVisible(true);
491
492     // The::globalActions()->value("download")->setEnabled(DownloadManager::instance()->itemForVideo(video) == 0);
493
494     QMainWindow* mainWindow = dynamic_cast<QMainWindow*>(window());
495     QString message = tr("Downloading %1").arg(video->title());
496     if (mainWindow) mainWindow->statusBar()->showMessage(message);
497 }
498
499 void MediaView::fullscreen() {
500     videoAreaWidget->setParent(0);
501     videoAreaWidget->showFullScreen();
502 }