]> git.sur5r.net Git - minitube/blob - src/MediaView.cpp
Added multimedia hotkeys support for stop, pause and skip actions
[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
7 namespace The {
8     QMap<QString, QAction*>* globalActions();
9     QMap<QString, QMenu*>* globalMenus();
10     QNetworkAccessManager* networkAccessManager();
11 }
12
13 MediaView::MediaView(QWidget *parent) : QWidget(parent) {
14
15     QBoxLayout *layout = new QHBoxLayout();
16     layout->setMargin(0);
17
18     splitter = new MiniSplitter(this);
19     splitter->setChildrenCollapsible(false);
20
21     sortBar = new THBlackBar(this);
22     mostRelevantAction = new QAction(tr("Most relevant"), this);
23     mostRelevantAction->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_1));
24     addAction(mostRelevantAction);
25     connect(mostRelevantAction, SIGNAL(triggered()), this, SLOT(searchMostRelevant()), Qt::QueuedConnection);
26     sortBar->addAction(mostRelevantAction);
27     mostRecentAction = new QAction(tr("Most recent"), this);
28     mostRecentAction->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_2));
29     addAction(mostRecentAction);
30     connect(mostRecentAction, SIGNAL(triggered()), this, SLOT(searchMostRecent()), Qt::QueuedConnection);
31     sortBar->addAction(mostRecentAction);
32     mostViewedAction = new QAction(tr("Most viewed"), this);
33     mostViewedAction->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_3));
34     addAction(mostViewedAction);
35     connect(mostViewedAction, SIGNAL(triggered()), this, SLOT(searchMostViewed()), Qt::QueuedConnection);
36     sortBar->addAction(mostViewedAction);
37
38     listView = new QListView(this);
39     listView->setItemDelegate(new PrettyItemDelegate(this));
40     listView->setSelectionMode(QAbstractItemView::ExtendedSelection);
41
42     // dragndrop
43     listView->setDragEnabled(true);
44     listView->setAcceptDrops(true);
45     listView->setDropIndicatorShown(true);
46     listView->setDragDropMode(QAbstractItemView::DragDrop);
47
48     // cosmetics
49     listView->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
50     listView->setFrameShape( QFrame::NoFrame );
51     listView->setAttribute(Qt::WA_MacShowFocusRect, false);
52     listView->setMinimumSize(320,240);
53
54     // respond to the user doubleclicking a playlist item
55     connect(listView, SIGNAL(activated(const QModelIndex &)), this, SLOT(itemActivated(const QModelIndex &)));
56
57     listModel = new ListModel(this);
58     connect(listModel, SIGNAL(activeRowChanged(int)), this, SLOT(activeRowChanged(int)));
59     // needed to restore the selection after dragndrop
60     connect(listModel, SIGNAL(needSelectionFor(QList<Video*>)), this, SLOT(selectVideos(QList<Video*>)));
61     listView->setModel(listModel);
62
63     connect(listView->selectionModel(),
64             SIGNAL(selectionChanged ( const QItemSelection & , const QItemSelection & )),
65             this, SLOT(selectionChanged ( const QItemSelection & , const QItemSelection & )));
66
67     playlistWidget = new PlaylistWidget(this, sortBar, listView);
68
69     splitter->addWidget(playlistWidget);
70
71     videoAreaWidget = new VideoAreaWidget(this);
72     videoAreaWidget->setMinimumSize(320,240);
73
74     videoWidget = new Phonon::VideoWidget(this);
75     videoAreaWidget->setVideoWidget(videoWidget);
76     videoAreaWidget->setListModel(listModel);
77
78     loadingWidget = new LoadingWidget(this);
79     videoAreaWidget->setLoadingWidget(loadingWidget);
80
81     splitter->addWidget(videoAreaWidget);
82
83     layout->addWidget(splitter);
84     setLayout(layout);
85
86     errorTimer = new QTimer(this);
87     errorTimer->setSingleShot(true);
88     errorTimer->setInterval(3000);
89     connect(errorTimer, SIGNAL(timeout()), SLOT(skipVideo()));
90 }
91
92 MediaView::~MediaView() {
93
94 }
95
96 void MediaView::initialize() {
97     connect(videoAreaWidget, SIGNAL(doubleClicked()), The::globalActions()->value("fullscreen"), SLOT(trigger()));
98     videoAreaWidget->setContextMenuPolicy(Qt::CustomContextMenu);
99     connect(videoAreaWidget, SIGNAL(customContextMenuRequested(QPoint)),
100             this, SLOT(showVideoContextMenu(QPoint)));
101 }
102
103 void MediaView::setMediaObject(Phonon::MediaObject *mediaObject) {
104     this->mediaObject = mediaObject;
105     Phonon::createPath(this->mediaObject, videoWidget);
106     // connect(mediaObject, SIGNAL(aboutToFinish()), this, SLOT(aboutToFinish()));
107     connect(mediaObject, SIGNAL(finished()), this, SLOT(skip()));
108     connect(mediaObject, SIGNAL(stateChanged(Phonon::State, Phonon::State)),
109             this, SLOT(stateChanged(Phonon::State, Phonon::State)));
110     connect(mediaObject, SIGNAL(currentSourceChanged(Phonon::MediaSource)),
111             this, SLOT(currentSourceChanged(Phonon::MediaSource)));
112     connect(mediaObject, SIGNAL(bufferStatus(int)), loadingWidget, SLOT(bufferStatus(int)));
113 }
114
115 void MediaView::search(SearchParams *searchParams) {
116     this->searchParams = searchParams;
117
118     // start serching for videos
119     listModel->search(searchParams);
120
121     // this implies that the enum and the bar action order is the same
122     sortBar->setCheckedAction(searchParams->sortBy()-1);
123
124     listView->setFocus();
125
126     loadingWidget->clear();
127 }
128
129 void MediaView::disappear() {
130     timerPlayFlag = true;
131 }
132
133 void MediaView::handleError(QString message) {
134     videoAreaWidget->showError(message);
135     skippedVideo = listModel->activeVideo();
136     // recover from errors by skipping to the next video
137     errorTimer->start(2000);
138 }
139
140 void MediaView::stateChanged(Phonon::State newState, Phonon::State /*oldState*/)
141 {
142
143     // qDebug() << "Phonon state: " << newState << oldState;
144
145     switch (newState) {
146
147          case Phonon::ErrorState:
148         qDebug() << "Phonon error:" << mediaObject->errorString() << mediaObject->errorType();
149         handleError(mediaObject->errorString());
150         break;
151
152          case Phonon::PlayingState:
153         qDebug("playing");
154         videoAreaWidget->showVideo();
155         break;
156
157          case Phonon::StoppedState:
158         qDebug("stopped");
159         // play() has already been called when setting the source
160         // but Phonon on Linux needs a little more help to start playback
161         mediaObject->play();
162
163         // Workaround for Mac playback start problem
164         if (!timerPlayFlag) {
165             QTimer::singleShot(1000, this, SLOT(timerPlay()));
166         }
167
168         break;
169
170          case Phonon::PausedState:
171         qDebug("paused");
172         break;
173
174          case Phonon::BufferingState:
175         qDebug("buffering");
176         break;
177
178          case Phonon::LoadingState:
179         qDebug("loading");
180         break;
181
182          default:
183         ;
184     }
185 }
186
187 void MediaView::pause() {
188     // qDebug() << "pause() called" << mediaObject->state();
189     switch( mediaObject->state() ) {
190     case Phonon::PlayingState:
191         mediaObject->pause();
192         break;
193     default:
194         mediaObject->play();
195         break;
196     }
197 }
198
199 void MediaView::stop() {
200     listModel->abortSearch();
201     mediaObject->stop();
202     mediaObject->clear();
203 }
204
205 void MediaView::activeRowChanged(int row) {
206     Video *video = listModel->videoAt(row);
207     if (!video) return;
208
209     // immediately show the loading widget
210     videoAreaWidget->showLoading(video);
211
212     // mediaObject->pause();
213
214     connect(video, SIGNAL(gotStreamUrl(QUrl)), SLOT(gotStreamUrl(QUrl)));
215     // TODO handle signal in a proper slot and impl item error status
216     connect(video, SIGNAL(errorStreamUrl(QString)), SLOT(handleError(QString)));
217     video->loadStreamUrl();
218
219     // reset the timer flag
220     timerPlayFlag = false;
221
222     // video title in the statusbar
223     QMainWindow* mainWindow = dynamic_cast<QMainWindow*>(qApp->topLevelWidgets().first());
224     if (mainWindow) mainWindow->statusBar()->showMessage(video->title());
225
226     // see you in gotStreamUrl...
227
228 }
229
230 void MediaView::gotStreamUrl(QUrl streamUrl) {
231
232     // go!
233     mediaObject->setCurrentSource(streamUrl);
234     mediaObject->play();
235
236     // ensure we always have 10 videos ahead
237     listModel->searchNeeded();
238
239     // ensure active item is visible
240     int row = listModel->activeRow();
241     if (row != -1) {
242         QModelIndex index = listModel->index(row, 0, QModelIndex());
243         listView->scrollTo(index, QAbstractItemView::EnsureVisible);
244     }
245
246 }
247
248 void MediaView::itemActivated(const QModelIndex &index) {
249     if (listModel->rowExists(index.row()))
250         listModel->setActiveRow(index.row());
251     // the user doucleclicked on the "Search More" item
252     else listModel->searchMore();
253 }
254
255 void MediaView::aboutToFinish() {
256     /*
257     int nextRow = listModel->nextRow();
258     if (nextRow == -1) return;
259     Video* video = listModel->videoAt(nextRow);
260     QUrl streamUrl = video->streamUrl();
261     qDebug() << "Enqueing" << streamUrl;
262     mediaObject->enqueue(streamUrl);
263     */
264 }
265
266 void MediaView::currentSourceChanged(const Phonon::MediaSource source) {
267     qDebug() << "Source changed:" << source.url();
268 }
269
270
271 void MediaView::skipVideo() {
272     // skippedVideo is useful for DELAYED skip operations
273     // in order to be sure that we're skipping the video we wanted
274     // and not another one
275     if (skippedVideo) {
276         if (listModel->activeVideo() != skippedVideo) {
277             qDebug() << "Skip of video canceled";
278             return;
279         }
280         int nextRow = listModel->rowForVideo(skippedVideo);
281         nextRow++;
282         if (nextRow == -1) return;
283         listModel->setActiveRow(nextRow);
284     }
285 }
286
287 void MediaView::skip() {
288     int nextRow = listModel->nextRow();
289     if (nextRow == -1) return;
290     listModel->setActiveRow(nextRow);
291 }
292
293 void MediaView::openWebPage() {
294     Video* video = listModel->activeVideo();
295     if (!video) return;
296     mediaObject->pause();
297     QDesktopServices::openUrl(video->webpage());
298 }
299
300 void MediaView::removeSelected() {
301     if (!listView->selectionModel()->hasSelection()) return;
302     QModelIndexList indexes = listView->selectionModel()->selectedIndexes();
303     listModel->removeIndexes(indexes);
304 }
305
306 void MediaView::selectVideos(QList<Video*> videos) {
307     foreach (Video *video, videos) {
308         QModelIndex index = listModel->indexForVideo(video);
309         listView->selectionModel()->select(index, QItemSelectionModel::Select);
310         listView->scrollTo(index, QAbstractItemView::EnsureVisible);
311     }
312 }
313
314 void MediaView::selectionChanged(const QItemSelection & /*selected*/, const QItemSelection & /*deselected*/) {
315     const bool gotSelection = listView->selectionModel()->hasSelection();
316     The::globalActions()->value("remove")->setEnabled(gotSelection);
317     The::globalActions()->value("moveUp")->setEnabled(gotSelection);
318     The::globalActions()->value("moveDown")->setEnabled(gotSelection);
319 }
320
321 void MediaView::moveUpSelected() {
322     if (!listView->selectionModel()->hasSelection()) return;
323     QModelIndexList indexes = listView->selectionModel()->selectedIndexes();
324     listModel->move(indexes, true);
325 }
326
327 void MediaView::moveDownSelected() {
328     if (!listView->selectionModel()->hasSelection()) return;
329     QModelIndexList indexes = listView->selectionModel()->selectedIndexes();
330     listModel->move(indexes, false);
331 }
332
333 void MediaView::showVideoContextMenu(QPoint point) {
334     The::globalMenus()->value("video")->popup(videoWidget->mapToGlobal(point));
335 }
336
337 void MediaView::searchMostRelevant() {
338     searchParams->setSortBy(SearchParams::SortByRelevance);
339     search(searchParams);
340 }
341
342 void MediaView::searchMostRecent() {
343     searchParams->setSortBy(SearchParams::SortByNewest);
344     search(searchParams);
345 }
346
347 void MediaView::searchMostViewed() {
348     searchParams->setSortBy(SearchParams::SortByViewCount);
349     search(searchParams);
350 }
351
352 void MediaView::setPlaylistVisible(bool visible) {
353     playlistWidget->setVisible(visible);
354 }
355
356 void MediaView::timerPlay() {
357     // qDebug() << mediaObject->currentTime();
358     // Workaround Phonon bug on Mac OSX
359     if (mediaObject->currentTime() <= 0 && mediaObject->state() == Phonon::PlayingState) {
360         mediaObject->pause();
361         mediaObject->play();
362     }
363 }