X-Git-Url: https://git.sur5r.net/?a=blobdiff_plain;f=src%2FMediaView.cpp;h=a77fba219122456205572e7d42eb4b3f5746ccec;hb=3c8c537559e6bef5f019196b0989e95863ddd3ee;hp=739c595a048b4bafd7449409c2d1fa57f550f6cf;hpb=7b529d6a918efe39ca6d63201fcdb954a3c881b4;p=minitube diff --git a/src/MediaView.cpp b/src/MediaView.cpp index 739c595..a77fba2 100644 --- a/src/MediaView.cpp +++ b/src/MediaView.cpp @@ -1,28 +1,37 @@ #include "MediaView.h" +#include "playlistview.h" #include "playlist/PrettyItemDelegate.h" #include "networkaccess.h" #include "videowidget.h" #include "minisplitter.h" #include "constants.h" #include "downloadmanager.h" +#include "downloaditem.h" +#include "MainWindow.h" +#include "temporary.h" namespace The { - QMap* globalActions(); - QMap* globalMenus(); - QNetworkAccessManager* networkAccessManager(); +NetworkAccess* http(); +} + +namespace The { +QMap* globalActions(); +QMap* globalMenus(); +QNetworkAccessManager* networkAccessManager(); } MediaView::MediaView(QWidget *parent) : QWidget(parent) { reallyStopped = false; + downloadItem = 0; - QBoxLayout *layout = new QHBoxLayout(); + QBoxLayout *layout = new QVBoxLayout(); layout->setMargin(0); splitter = new MiniSplitter(this); splitter->setChildrenCollapsible(false); - sortBar = new THBlackBar(this); + sortBar = new SegmentedControl(this); mostRelevantAction = new QAction(tr("Most relevant"), this); QKeySequence keySequence(Qt::CTRL + Qt::Key_1); mostRelevantAction->setShortcut(keySequence); @@ -45,7 +54,7 @@ MediaView::MediaView(QWidget *parent) : QWidget(parent) { connect(mostViewedAction, SIGNAL(triggered()), this, SLOT(searchMostViewed()), Qt::QueuedConnection); sortBar->addAction(mostViewedAction); - listView = new QListView(this); + listView = new PlaylistView(this); listView->setItemDelegate(new PrettyItemDelegate(this)); listView->setSelectionMode(QAbstractItemView::ExtendedSelection); @@ -75,6 +84,8 @@ MediaView::MediaView(QWidget *parent) : QWidget(parent) { SIGNAL(selectionChanged ( const QItemSelection & , const QItemSelection & )), this, SLOT(selectionChanged ( const QItemSelection & , const QItemSelection & ))); + connect(listView, SIGNAL(authorPushed(QModelIndex)), SLOT(authorPushed(QModelIndex))); + playlistWidget = new PlaylistWidget(this, sortBar, listView); splitter->addWidget(playlistWidget); @@ -82,7 +93,7 @@ MediaView::MediaView(QWidget *parent) : QWidget(parent) { videoAreaWidget = new VideoAreaWidget(this); videoAreaWidget->setMinimumSize(320,240); -#ifdef APP_MAC +#ifdef APP_MAC_NO // mouse autohide does not work on the Mac (no mouseMoveEvent) videoWidget = new Phonon::VideoWidget(this); #else @@ -100,6 +111,9 @@ MediaView::MediaView(QWidget *parent) : QWidget(parent) { layout->addWidget(splitter); setLayout(layout); + splitter->setStretchFactor(0, 1); + splitter->setStretchFactor(1, 6); + // restore splitter state QSettings settings; splitter->restoreState(settings.value("splitter").toByteArray()); @@ -117,7 +131,6 @@ MediaView::MediaView(QWidget *parent) : QWidget(parent) { #ifdef APP_DEMO demoTimer = new QTimer(this); demoTimer->setSingleShot(true); - demoTimer->setInterval(60000); connect(demoTimer, SIGNAL(timeout()), SLOT(demoMessage())); #endif @@ -125,20 +138,23 @@ MediaView::MediaView(QWidget *parent) : QWidget(parent) { void MediaView::initialize() { connect(videoAreaWidget, SIGNAL(doubleClicked()), The::globalActions()->value("fullscreen"), SLOT(trigger())); + + /* videoAreaWidget->setContextMenuPolicy(Qt::CustomContextMenu); connect(videoAreaWidget, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(showVideoContextMenu(QPoint))); + */ } void MediaView::setMediaObject(Phonon::MediaObject *mediaObject) { this->mediaObject = mediaObject; Phonon::createPath(this->mediaObject, videoWidget); - connect(mediaObject, SIGNAL(finished()), this, SLOT(skip())); + connect(mediaObject, SIGNAL(finished()), this, SLOT(playbackFinished())); connect(mediaObject, SIGNAL(stateChanged(Phonon::State, Phonon::State)), this, SLOT(stateChanged(Phonon::State, Phonon::State))); connect(mediaObject, SIGNAL(currentSourceChanged(Phonon::MediaSource)), this, SLOT(currentSourceChanged(Phonon::MediaSource))); - connect(mediaObject, SIGNAL(bufferStatus(int)), loadingWidget, SLOT(bufferStatus(int))); + // connect(mediaObject, SIGNAL(bufferStatus(int)), loadingWidget, SLOT(bufferStatus(int))); } void MediaView::search(SearchParams *searchParams) { @@ -147,8 +163,6 @@ void MediaView::search(SearchParams *searchParams) { #ifdef APP_DEMO demoTimer->stop(); #endif - - videoAreaWidget->clear(); workaroundTimer->stop(); errorTimer->stop(); @@ -162,70 +176,89 @@ void MediaView::search(SearchParams *searchParams) { listView->setFocus(); + QString keyword = searchParams->keywords(); + QString display = keyword; + if (keyword.startsWith("http://") || keyword.startsWith("https://")) { + int separator = keyword.indexOf("|"); + if (separator > 0 && separator + 1 < keyword.length()) { + display = keyword.mid(separator+1); + } + } + +} + +void MediaView::appear() { + listView->setFocus(); } void MediaView::disappear() { timerPlayFlag = true; } -void MediaView::handleError(QString message) { +void MediaView::handleError(QString /* message */) { + + QTimer::singleShot(100, this, SLOT(startPlaying())); + + /* videoAreaWidget->showError(message); skippedVideo = listModel->activeVideo(); // recover from errors by skipping to the next video errorTimer->start(2000); + */ } void MediaView::stateChanged(Phonon::State newState, Phonon::State /*oldState*/) { // qDebug() << "Phonon state: " << newState << oldState; + // slider->setEnabled(newState == Phonon::PlayingState); switch (newState) { case Phonon::ErrorState: qDebug() << "Phonon error:" << mediaObject->errorString() << mediaObject->errorType(); - handleError(mediaObject->errorString()); + if (mediaObject->errorType() == Phonon::FatalError) + handleError(mediaObject->errorString()); break; case Phonon::PlayingState: - //qDebug("playing"); + // qDebug("playing"); videoAreaWidget->showVideo(); break; case Phonon::StoppedState: - //qDebug("stopped"); + // qDebug("stopped"); // play() has already been called when setting the source // but Phonon on Linux needs a little more help to start playback - if (!reallyStopped) mediaObject->play(); + // if (!reallyStopped) mediaObject->play(); #ifdef APP_MAC // Workaround for Mac playback start problem if (!timerPlayFlag) { - workaroundTimer->start(); + // workaroundTimer->start(); } #endif break; - case Phonon::PausedState: - //qDebug("paused"); + case Phonon::PausedState: + qDebug("paused"); break; - case Phonon::BufferingState: - //qDebug("buffering"); + case Phonon::BufferingState: + qDebug("buffering"); break; - case Phonon::LoadingState: - //qDebug("loading"); + case Phonon::LoadingState: + qDebug("loading"); break; - default: - ; } } void MediaView::pause() { // qDebug() << "pause() called" << mediaObject->state(); + switch( mediaObject->state() ) { case Phonon::PlayingState: mediaObject->pause(); @@ -234,6 +267,11 @@ void MediaView::pause() { mediaObject->play(); break; } + +} + +QRegExp MediaView::wordRE(QString s) { + return QRegExp("\\W" + s + "\\W?", Qt::CaseInsensitive); } void MediaView::stop() { @@ -244,6 +282,11 @@ void MediaView::stop() { workaroundTimer->stop(); errorTimer->stop(); listView->selectionModel()->clearSelection(); + if (downloadItem) { + downloadItem->stop(); + delete downloadItem; + downloadItem = 0; + } } void MediaView::activeRowChanged(int row) { @@ -257,12 +300,20 @@ void MediaView::activeRowChanged(int row) { workaroundTimer->stop(); errorTimer->stop(); + mediaObject->pause(); + if (downloadItem) { + downloadItem->stop(); + delete downloadItem; + downloadItem = 0; + } + // slider->setMinimum(0); + // immediately show the loading widget videoAreaWidget->showLoading(video); - connect(video, SIGNAL(gotStreamUrl(QUrl)), SLOT(gotStreamUrl(QUrl))); + connect(video, SIGNAL(gotStreamUrl(QUrl)), SLOT(gotStreamUrl(QUrl)), Qt::UniqueConnection); // TODO handle signal in a proper slot and impl item error status - connect(video, SIGNAL(errorStreamUrl(QString)), SLOT(handleError(QString))); + connect(video, SIGNAL(errorStreamUrl(QString)), SLOT(handleError(QString)), Qt::UniqueConnection); video->loadStreamUrl(); @@ -273,7 +324,19 @@ void MediaView::activeRowChanged(int row) { QMainWindow* mainWindow = dynamic_cast(window()); if (mainWindow) mainWindow->statusBar()->showMessage(video->title()); + + // ensure active item is visible + // int row = listModel->activeRow(); + if (row != -1) { + QModelIndex index = listModel->index(row, 0, QModelIndex()); + listView->scrollTo(index, QAbstractItemView::EnsureVisible); + } + + // enable/disable actions The::globalActions()->value("download")->setEnabled(DownloadManager::instance()->itemForVideo(video) == 0); + The::globalActions()->value("skip")->setEnabled(true); + The::globalActions()->value("previous")->setEnabled(row > 0); + The::globalActions()->value("stopafterthis")->setEnabled(true); // see you in gotStreamUrl... @@ -289,9 +352,83 @@ void MediaView::gotStreamUrl(QUrl streamUrl) { } video->disconnect(this); + QString tempFile = Temporary::filename(); + + Video *videoCopy = video->clone(); + if (downloadItem) { + downloadItem->stop(); + delete downloadItem; + } + downloadItem = new DownloadItem(videoCopy, streamUrl, tempFile, this); + connect(downloadItem, SIGNAL(statusChanged()), SLOT(downloadStatusChanged()), Qt::UniqueConnection); + // connect(downloadItem, SIGNAL(progress(int)), SLOT(downloadProgress(int))); + connect(downloadItem, SIGNAL(bufferProgress(int)), loadingWidget, SLOT(bufferStatus(int)), Qt::UniqueConnection); + // connect(downloadItem, SIGNAL(finished()), SLOT(itemFinished())); + connect(video, SIGNAL(errorStreamUrl(QString)), SLOT(handleError(QString)), Qt::UniqueConnection); + connect(downloadItem, SIGNAL(error(QString)), SLOT(handleError(QString)), Qt::UniqueConnection); + downloadItem->start(); + +} + +/* +void MediaView::downloadProgress(int percent) { + MainWindow* mainWindow = dynamic_cast(window()); + + mainWindow->getSeekSlider()->setStyleSheet(" QSlider::groove:horizontal {" + "border: 1px solid #999999;" + // "border-left: 50px solid rgba(255, 0, 0, 128);" + "height: 8px;" + "background: qlineargradient(x1:0, y1:0, x2:.5, y2:0, stop:0 rgba(255, 0, 0, 92), stop:" + + QString::number(percent/100.0) + + + " rgba(255, 0, 0, 92), stop:" + QString::number((percent+1)/100.0) + " transparent, stop:1 transparent);" + "margin: 2px 0;" + "}" + "QSlider::handle:horizontal {" + "background: qlineargradient(x1:0, y1:0, x2:1, y2:1, stop:0 #b4b4b4, stop:1 #8f8f8f);" + "border: 1px solid #5c5c5c;" + "width: 16px;" + "height: 16px;" + "margin: -2px 0;" + "border-radius: 8px;" + "}" + + ); +} + +*/ + +void MediaView::downloadStatusChanged() { + switch(downloadItem->status()) { + case Downloading: + startPlaying(); + break; + case Starting: + // qDebug() << "Starting"; + break; + case Finished: + // qDebug() << "Finished" << mediaObject->state(); + // if (mediaObject->state() == Phonon::StoppedState) startPlaying(); + break; + case Failed: + // qDebug() << "Failed"; + case Idle: + // qDebug() << "Idle"; + break; + } +} + +void MediaView::startPlaying() { + if (reallyStopped) return; + if (!downloadItem) { + skip(); + return; + } + // go! - qDebug() << "Playing" << streamUrl.toString(); - mediaObject->setCurrentSource(streamUrl); + QString source = downloadItem->currentFilename(); + qDebug() << "Playing" << source; + mediaObject->setCurrentSource(source); mediaObject->play(); // ensure we always have 10 videos ahead @@ -305,16 +442,27 @@ void MediaView::gotStreamUrl(QUrl streamUrl) { } #ifdef APP_DEMO - demoTimer->start(); + demoTimer->start(60000); #endif } void MediaView::itemActivated(const QModelIndex &index) { - if (listModel->rowExists(index.row())) - listModel->setActiveRow(index.row()); + if (listModel->rowExists(index.row())) { + + // if it's the current video, just rewind and play + Video *activeVideo = listModel->activeVideo(); + Video *video = listModel->videoAt(index.row()); + if (activeVideo && video && activeVideo == video) { + mediaObject->seek(0); + mediaObject->play(); + } else listModel->setActiveRow(index.row()); + // the user doubleclicked on the "Search More" item - else listModel->searchMore(); + } else { + listModel->searchMore(); + listView->selectionModel()->clearSelection(); + } } void MediaView::currentSourceChanged(const Phonon::MediaSource /* source */ ) { @@ -343,6 +491,31 @@ void MediaView::skip() { listModel->setActiveRow(nextRow); } +void MediaView::skipBackward() { + int prevRow = listModel->previousRow(); + if (prevRow == -1) return; + listModel->setActiveRow(prevRow); +} + +void MediaView::playbackFinished() { + // qDebug() << "finished" << mediaObject->currentTime() << mediaObject->totalTime(); + // add 10 secs for imprecise Phonon backends (VLC, Xine) + if (mediaObject->currentTime() + 10000 < mediaObject->totalTime()) { + // mediaObject->seek(mediaObject->currentTime()); + QTimer::singleShot(500, this, SLOT(playbackResume())); + } else { + QAction* stopAfterThisAction = The::globalActions()->value("stopafterthis"); + if (stopAfterThisAction->isChecked()) { + stopAfterThisAction->setChecked(false); + } else skip(); + } +} + +void MediaView::playbackResume() { + mediaObject->seek(mediaObject->currentTime()); + mediaObject->play(); +} + void MediaView::openWebPage() { Video* video = listModel->activeVideo(); if (!video) return; @@ -354,7 +527,6 @@ void MediaView::copyWebPage() { Video* video = listModel->activeVideo(); if (!video) return; QString address = video->webpage().toString(); - address.remove("&feature=youtube_gdata"); QApplication::clipboard()->setText(address); QMainWindow* mainWindow = dynamic_cast(window()); QString message = tr("You can now paste the YouTube link into another application"); @@ -364,9 +536,9 @@ void MediaView::copyWebPage() { void MediaView::copyVideoLink() { Video* video = listModel->activeVideo(); if (!video) return; - QApplication::clipboard()->setText(video->getStreamUrl().toString()); + QApplication::clipboard()->setText(video->getStreamUrl().toEncoded()); QString message = tr("You can now paste the video stream URL into another application") - + ". " + tr("The link will be valid only for a limited time."); + + ". " + tr("The link will be valid only for a limited time."); QMainWindow* mainWindow = dynamic_cast(window()); if (mainWindow) mainWindow->statusBar()->showMessage(message); } @@ -456,27 +628,53 @@ void MediaView::saveSplitterState() { } #ifdef APP_DEMO + +static QPushButton *continueButton; + void MediaView::demoMessage() { if (mediaObject->state() != Phonon::PlayingState) return; mediaObject->pause(); - QMessageBox msgBox; + QMessageBox msgBox(this); msgBox.setIconPixmap(QPixmap(":/images/app.png").scaled(64, 64, Qt::KeepAspectRatio, Qt::SmoothTransformation)); - msgBox.setText(tr("This is just the demo version of %1.").arg(Constants::APP_NAME)); + msgBox.setText(tr("This is just the demo version of %1.").arg(Constants::NAME)); msgBox.setInformativeText(tr("It allows you to test the application and see if it works for you.")); msgBox.setModal(true); + // make it a "sheet" on the Mac + msgBox.setWindowModality(Qt::WindowModal); - QPushButton *quitButton = msgBox.addButton(tr("Continue"), QMessageBox::RejectRole); + continueButton = msgBox.addButton("5", QMessageBox::RejectRole); + continueButton->setEnabled(false); QPushButton *buyButton = msgBox.addButton(tr("Get the full version"), QMessageBox::ActionRole); + QTimeLine *timeLine = new QTimeLine(6000, this); + timeLine->setCurveShape(QTimeLine::LinearCurve); + timeLine->setFrameRange(5, 0); + connect(timeLine, SIGNAL(frameChanged(int)), SLOT(updateContinueButton(int))); + timeLine->start(); + msgBox.exec(); if (msgBox.clickedButton() == buyButton) { - QDesktopServices::openUrl(QString(Constants::WEBSITE) + "#download"); + QDesktopServices::openUrl(QUrl(QString(Constants::WEBSITE) + "#download")); } else { mediaObject->play(); + demoTimer->start(600000); + } + + delete timeLine; + +} + +void MediaView::updateContinueButton(int value) { + if (value == 0) { + continueButton->setText(tr("Continue")); + continueButton->setEnabled(true); + } else { + continueButton->setText(QString::number(value)); } } + #endif void MediaView::downloadVideo() { @@ -500,3 +698,154 @@ void MediaView::fullscreen() { videoAreaWidget->setParent(0); videoAreaWidget->showFullScreen(); } + +/* +void MediaView::setSlider(QSlider *slider) { + this->slider = slider; + // slider->setEnabled(false); + slider->setTracking(false); + // connect(slider, SIGNAL(valueChanged(int)), SLOT(sliderMoved(int))); +} + +void MediaView::sliderMoved(int value) { + qDebug() << __func__; + int sliderPercent = (value * 100) / (slider->maximum() - slider->minimum()); + qDebug() << slider->minimum() << value << slider->maximum(); + if (sliderPercent <= downloadItem->currentPercent()) { + qDebug() << sliderPercent << downloadItem->currentPercent(); + mediaObject->seek(value); + } else { + seekTo(value); + } +} + +void MediaView::seekTo(int value) { + qDebug() << __func__; + mediaObject->pause(); + workaroundTimer->stop(); + errorTimer->stop(); + // mediaObject->clear(); + + QString tempDir = QDesktopServices::storageLocation(QDesktopServices::TempLocation); + QString tempFile = tempDir + "/minitube" + QString::number(value) + ".mp4"; + if (!QFile::remove(tempFile)) { + qDebug() << "Cannot remove temp file"; + } + Video *videoCopy = downloadItem->getVideo()->clone(); + QUrl streamUrl = videoCopy->getStreamUrl(); + streamUrl.addQueryItem("begin", QString::number(value)); + if (downloadItem) delete downloadItem; + downloadItem = new DownloadItem(videoCopy, streamUrl, tempFile, this); + connect(downloadItem, SIGNAL(statusChanged()), SLOT(downloadStatusChanged())); + // connect(downloadItem, SIGNAL(finished()), SLOT(itemFinished())); + downloadItem->start(); + + // slider->setMinimum(value); + +} + +*/ + +void MediaView::findVideoParts() { + + // parts + Video* video = listModel->activeVideo(); + if (!video) return; + + QString query = video->title(); + + static QString optionalSpace = "\\s*"; + static QString staticCounterSeparators = "[\\/\\-]"; + QString counterSeparators = "( of | " + + tr("of", "Used in video parts, as in '2 of 3'") + + " |" + staticCounterSeparators + ")"; + + // numbers from 1 to 15 + static QString counterNumber = "([1-9]|1[0-5])"; + + // query.remove(QRegExp(counterSeparators + optionalSpace + counterNumber)); + query.remove(QRegExp(counterNumber + optionalSpace + counterSeparators + optionalSpace + counterNumber)); + query.remove(wordRE("pr?t\\.?" + optionalSpace + counterNumber)); + query.remove(wordRE("ep\\.?" + optionalSpace + counterNumber)); + query.remove(wordRE("part" + optionalSpace + counterNumber)); + query.remove(wordRE("episode" + optionalSpace + counterNumber)); + query.remove(wordRE(tr("part", "This is for video parts, as in 'Cool video - part 1'") + + optionalSpace + counterNumber)); + query.remove(wordRE(tr("episode", "This is for video parts, as in 'Cool series - episode 1'") + + optionalSpace + counterNumber)); + query.remove(QRegExp("[\\(\\)\\[\\]]")); + +#define NUMBERS "one|two|three|four|five|six|seven|eight|nine|ten" + + QRegExp englishNumberRE = QRegExp(QLatin1String(".*(") + NUMBERS + ").*", Qt::CaseInsensitive); + // bool numberAsWords = englishNumberRE.exactMatch(query); + query.remove(englishNumberRE); + + QRegExp localizedNumberRE = QRegExp(QLatin1String(".*(") + tr(NUMBERS) + ").*", Qt::CaseInsensitive); + // if (!numberAsWords) numberAsWords = localizedNumberRE.exactMatch(query); + query.remove(localizedNumberRE); + + SearchParams *searchParams = new SearchParams(); + searchParams->setTransient(true); + searchParams->setKeywords(query); + searchParams->setAuthor(video->author()); + + /* + if (!numberAsWords) { + qDebug() << "We don't have number as words"; + // searchParams->setSortBy(SearchParams::SortByNewest); + // TODO searchParams->setReverseOrder(true); + // TODO searchParams->setMax(50); + } + */ + + search(searchParams); + +} + +void MediaView::shareViaTwitter() { + Video* video = listModel->activeVideo(); + if (!video) return; + QUrl url("https://twitter.com/intent/tweet"); + url.addQueryItem("via", "minitubeapp"); + url.addQueryItem("text", video->title()); + url.addQueryItem("url", video->webpage().toString()); + QDesktopServices::openUrl(url); +} + +void MediaView::shareViaFacebook() { + Video* video = listModel->activeVideo(); + if (!video) return; + QUrl url("https://www.facebook.com/sharer.php"); + url.addQueryItem("t", video->title()); + url.addQueryItem("u", video->webpage().toString()); + QDesktopServices::openUrl(url); +} + +void MediaView::shareViaEmail() { + Video* video = listModel->activeVideo(); + if (!video) return; + QUrl url("mailto:"); + url.addQueryItem("subject", video->title()); + QString body = video->title() + "\n" + + video->webpage().toString() + "\n\n" + + tr("Sent from %1").arg(Constants::NAME) + "\n" + + Constants::WEBSITE; + url.addQueryItem("body", body); + QDesktopServices::openUrl(url); +} + +void MediaView::authorPushed(QModelIndex index) { + Video* video = listModel->videoAt(index.row()); + if (!video) return; + + QString channel = video->author(); + if (channel.isEmpty()) return; + + SearchParams *searchParams = new SearchParams(); + searchParams->setAuthor(channel); + searchParams->setSortBy(SearchParams::SortByNewest); + + // go! + search(searchParams); +}