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