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