From 14548e6683b3d50ff10aa25d974da0fbd475b51c Mon Sep 17 00:00:00 2001 From: Flavio Date: Fri, 10 Dec 2010 11:14:07 +0100 Subject: [PATCH] Big changes in playback Cannot get Phonon backends (nor any other external player) to play the YouTube streams, there is probably a check on a costant UA and request headers on their side. Now Minitube downloads to a temporary file, then Phonon plays from it. --- src/MainWindow.cpp | 27 +++++++- src/MainWindow.h | 2 + src/MediaView.cpp | 158 ++++++++++++++++++++++++++++++++++++++++-- src/MediaView.h | 18 ++++- src/downloaditem.cpp | 14 +++- src/downloaditem.h | 1 + src/networkaccess.cpp | 25 +++++-- src/networkaccess.h | 2 + 8 files changed, 231 insertions(+), 16 deletions(-) diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index c87c398..1ef752b 100755 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -45,6 +45,7 @@ MainWindow::MainWindow() : createStatusBar(); initPhonon(); + // mediaView->setSlider(slider); mediaView->setMediaObject(mediaObject); // remove that useless menu/toolbar context menu @@ -395,12 +396,24 @@ void MainWindow::createToolBars() { mainToolBar->addWidget(new Spacer()); seekSlider = new Phonon::SeekSlider(this); +#ifdef Q_WS_X11 + seekSlider->setDisabled(true); +#endif seekSlider->setIconVisible(false); seekSlider->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred); mainToolBar->addWidget(seekSlider); mainToolBar->addWidget(new Spacer()); +/* + slider = new QSlider(this); + slider->setOrientation(Qt::Horizontal); + slider->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred); + mainToolBar->addWidget(slider); +*/ + + mainToolBar->addWidget(new Spacer()); + totalTime = new QLabel(mainToolBar); totalTime->setFont(smallerFont); mainToolBar->addWidget(totalTime); @@ -453,7 +466,7 @@ void MainWindow::readSettings() { restoreGeometry(settings.value("geometry").toByteArray()); #ifdef APP_MAC if (!isMaximized()) - move(x(), y() + mainToolBar->height() + 8); + move(x(), y() + 10); #endif setDefinitionMode(settings.value("definition", VideoDefinition::getDefinitionNames().first()).toString()); audioOutput->setVolume(settings.value("volume", 1).toDouble()); @@ -814,6 +827,11 @@ void MainWindow::tick(qint64 time) { const qint64 remainingTime = mediaObject->remainingTime(); currentTime->setStatusTip(tr("Remaining time: %1").arg(formatTime(remainingTime))); + /* + slider->blockSignals(true); + slider->setValue(time/1000); + slider->blockSignals(false); + */ } void MainWindow::totalTimeChanged(qint64 time) { @@ -822,6 +840,13 @@ void MainWindow::totalTimeChanged(qint64 time) { return; } totalTime->setText(formatTime(time)); + + /* + slider->blockSignals(true); + slider->setMaximum(time/1000); + slider->blockSignals(false); + */ + } QString MainWindow::formatTime(qint64 time) { diff --git a/src/MainWindow.h b/src/MainWindow.h index e90dbf8..203a214 100755 --- a/src/MainWindow.h +++ b/src/MainWindow.h @@ -21,6 +21,7 @@ class MainWindow : public QMainWindow { public: MainWindow(); ~MainWindow(); + Phonon::SeekSlider* getSeekSlider() { return seekSlider; } protected: void closeEvent(QCloseEvent *); @@ -125,6 +126,7 @@ private: // phonon Phonon::SeekSlider *seekSlider; + // QSlider *slider; Phonon::VolumeSlider *volumeSlider; Phonon::MediaObject *mediaObject; Phonon::AudioOutput *audioOutput; diff --git a/src/MediaView.cpp b/src/MediaView.cpp index 739c595..8890ace 100644 --- a/src/MediaView.cpp +++ b/src/MediaView.cpp @@ -5,6 +5,12 @@ #include "minisplitter.h" #include "constants.h" #include "downloadmanager.h" +#include "downloaditem.h" +#include "MainWindow.h" + +namespace The { + NetworkAccess* http(); +} namespace The { QMap* globalActions(); @@ -15,6 +21,7 @@ namespace The { MediaView::MediaView(QWidget *parent) : QWidget(parent) { reallyStopped = false; + downloadItem = 0; QBoxLayout *layout = new QHBoxLayout(); layout->setMargin(0); @@ -152,6 +159,12 @@ void MediaView::search(SearchParams *searchParams) { workaroundTimer->stop(); errorTimer->stop(); + mediaObject->pause(); + if (downloadItem) { + delete downloadItem; + downloadItem = 0; + } + this->searchParams = searchParams; // start serching for videos @@ -162,6 +175,17 @@ void MediaView::search(SearchParams *searchParams) { listView->setFocus(); + + QString keyword = searchParams->keywords(); + QString display = keyword; + if (keyword.startsWith("http://")) { + int separator = keyword.indexOf("|"); + if (separator > 0 && separator + 1 < keyword.length()) { + display = keyword.mid(separator+1); + } + } + // tr("You're watching \"%1\"").arg(searchParams->keywords()) + } void MediaView::disappear() { @@ -179,12 +203,14 @@ 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: @@ -244,6 +270,10 @@ void MediaView::stop() { workaroundTimer->stop(); errorTimer->stop(); listView->selectionModel()->clearSelection(); + if (downloadItem) { + delete downloadItem; + downloadItem = 0; + } } void MediaView::activeRowChanged(int row) { @@ -257,6 +287,13 @@ void MediaView::activeRowChanged(int row) { workaroundTimer->stop(); errorTimer->stop(); + mediaObject->pause(); + if (downloadItem) { + delete downloadItem; + downloadItem = 0; + } + // slider->setMinimum(0); + // immediately show the loading widget videoAreaWidget->showLoading(video); @@ -289,9 +326,75 @@ void MediaView::gotStreamUrl(QUrl streamUrl) { } video->disconnect(this); + QString tempDir = QDesktopServices::storageLocation(QDesktopServices::TempLocation); + QString tempFile = tempDir + "/minitube.mp4"; + if (!QFile::remove(tempFile)) { + qDebug() << "Cannot remove temp file"; + } + + Video *videoCopy = video->clone(); + if (downloadItem) delete downloadItem; + downloadItem = new DownloadItem(videoCopy, streamUrl, tempFile, this); + connect(downloadItem, SIGNAL(statusChanged()), SLOT(downloadStatusChanged())); + // connect(downloadItem, SIGNAL(progress(int)), SLOT(downloadProgress(int))); + // connect(downloadItem, SIGNAL(finished()), SLOT(itemFinished())); + 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"; + break; + case Failed: + qDebug() << "Failed"; + case Idle: + qDebug() << "Idle"; + break; + } +} + +void MediaView::startPlaying() { + if (reallyStopped) return; + // go! - qDebug() << "Playing" << streamUrl.toString(); - mediaObject->setCurrentSource(streamUrl); + qDebug() << "Playing" << downloadItem->currentFilename(); + mediaObject->setCurrentSource(downloadItem->currentFilename()); mediaObject->play(); // ensure we always have 10 videos ahead @@ -364,7 +467,7 @@ 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."); QMainWindow* mainWindow = dynamic_cast(window()); @@ -500,3 +603,50 @@ 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); + +} + +*/ diff --git a/src/MediaView.h b/src/MediaView.h index 7a19d68..f61799a 100644 --- a/src/MediaView.h +++ b/src/MediaView.h @@ -13,6 +13,8 @@ #include "loadingwidget.h" #include "videoareawidget.h" +class DownloadItem; + namespace The { QMap* globalActions(); } @@ -32,13 +34,14 @@ public: QMap metadata() { QMap metadata; if (searchParams) { - metadata.insert("title", searchParams->keywords()); - metadata.insert("description", tr("You're watching \"%1\"").arg(searchParams->keywords())); + metadata.insert("title", ""); + metadata.insert("description", ""); } return metadata; } void setMediaObject(Phonon::MediaObject *mediaObject); + void setSlider(QSlider *slider); public slots: void search(SearchParams *searchParams); @@ -78,6 +81,14 @@ private slots: #ifdef APP_DEMO void demoMessage(); #endif + void startPlaying(); + void downloadStatusChanged(); + + /* + void downloadProgress(int percent); + void sliderMoved(int value); + void seekTo(int value); + */ private: @@ -114,6 +125,9 @@ private: QTimer *demoTimer; #endif + DownloadItem *downloadItem; + // QSlider *slider; + }; #endif // __MEDIAVIEW_H__ diff --git a/src/downloaditem.cpp b/src/downloaditem.cpp index ab92fc3..1219fb4 100644 --- a/src/downloaditem.cpp +++ b/src/downloaditem.cpp @@ -99,7 +99,6 @@ void DownloadItem::downloadReadyRead() { emit statusChanged(); } - m_status = Downloading; if (-1 == m_file.write(m_reply->readAll())) { /* downloadInfoLabel->setText(tr("Error saving: %1") @@ -108,7 +107,10 @@ void DownloadItem::downloadReadyRead() { */ } else { m_startedSaving = true; - if (m_finishedDownloading) + if (m_status != Downloading) { + // m_status = Downloading; + // emit statusChanged(); + } else if (m_finishedDownloading) requestFinished(); } } @@ -157,6 +159,14 @@ void DownloadItem::downloadProgress(qint64 bytesReceived, qint64 bytesTotal) { percent = bytesReceived * 100 / bytesTotal; } + // qDebug() << bytesReceived << bytesTotal; + if (m_status != Downloading + && bytesReceived > 1024 * 512 + && bytesReceived > bytesTotal * .01) { + m_status = Downloading; + emit statusChanged(); + } + emit progress(percent); // emit statusChanged(); } diff --git a/src/downloaditem.h b/src/downloaditem.h index 1d6b1f1..7ccf4db 100644 --- a/src/downloaditem.h +++ b/src/downloaditem.h @@ -31,6 +31,7 @@ public: double currentSpeed() const; int currentPercent() const { return percent; } Video* getVideo() const { return video; } + QString currentFilename() const { return m_file.fileName(); } DownloadItemStatus status() const { return m_status; } static QString formattedFilesize(qint64 size); static QString formattedSpeed(double speed); diff --git a/src/networkaccess.cpp b/src/networkaccess.cpp index 378cb1a..4c4058e 100644 --- a/src/networkaccess.cpp +++ b/src/networkaccess.cpp @@ -6,9 +6,13 @@ namespace The { NetworkAccess* http(); } +/* const QString USER_AGENT = QString(Constants::APP_NAME) + " " + Constants::VERSION + " (" + Constants::WEBSITE + ")"; +*/ + +const QString USER_AGENT = "Mozilla/5.0 (X11; U; Linux x86; en-US; rv:1.9.2.12) Gecko/20101028 Firefox/3.6.12"; NetworkReply::NetworkReply(QNetworkReply *networkReply) : QObject(networkReply) { this->networkReply = networkReply; @@ -54,24 +58,20 @@ void NetworkReply::requestError(QNetworkReply::NetworkError code) { NetworkAccess::NetworkAccess( QObject* parent) : QObject( parent ) {} -QNetworkReply* NetworkAccess::simpleGet(QUrl url, int operation) { +QNetworkReply* NetworkAccess::manualGet(QNetworkRequest request, int operation) { QNetworkAccessManager *manager = The::networkAccessManager(); - QNetworkRequest request(url); - request.setRawHeader("User-Agent", USER_AGENT.toUtf8()); - request.setRawHeader("Connection", "Keep-Alive"); - QNetworkReply *networkReply; switch (operation) { case QNetworkAccessManager::GetOperation: - qDebug() << "GET" << url.toString(); + qDebug() << "GET" << request.url().toEncoded(); networkReply = manager->get(request); break; case QNetworkAccessManager::HeadOperation: - qDebug() << "HEAD" << url.toString(); + qDebug() << "HEAD" << request.url().toEncoded(); networkReply = manager->head(request); break; @@ -86,7 +86,18 @@ QNetworkReply* NetworkAccess::simpleGet(QUrl url, int operation) { this, SLOT(error(QNetworkReply::NetworkError))); return networkReply; +} + +QNetworkReply* NetworkAccess::simpleGet(QUrl url, int operation) { + + QNetworkRequest request(url); + request.setRawHeader("User-Agent", USER_AGENT.toUtf8()); + request.setRawHeader("Accept-Charset", "ISO-8859-1,utf-8;q=0.7,*;q=0.7"); + request.setRawHeader("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"); + request.setRawHeader("Accept-Language", "en-us,en;q=0.5"); + // request.setRawHeader("Connection", "Keep-Alive"); + return manualGet(request, operation); } NetworkReply* NetworkAccess::get(const QUrl url) { diff --git a/src/networkaccess.h b/src/networkaccess.h index 64cf508..58e55e3 100644 --- a/src/networkaccess.h +++ b/src/networkaccess.h @@ -1,6 +1,7 @@ #ifndef NETWORKACCESS_H #define NETWORKACCESS_H +#include #include namespace The { @@ -35,6 +36,7 @@ class NetworkAccess : public QObject { public: NetworkAccess( QObject* parent=0); + QNetworkReply* manualGet(QNetworkRequest request, int operation = QNetworkAccessManager::GetOperation); QNetworkReply* simpleGet(QUrl url, int operation = QNetworkAccessManager::GetOperation); NetworkReply* get(QUrl url); NetworkReply* head(QUrl url); -- 2.39.5