1 /* VLC-REMOTE for MAEMO 5
2 * Copyright (C) 2010 Schutz Sacha <istdasklar@gmail.com>, Dru Moore <usr@dru-id.co.uk>, Yann Nave <yannux@onbebop.net>
3 * This program is free software; you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License version 2,
5 * or (at your option) any later version, as published by the Free
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details
13 * You should have received a copy of the GNU General Public
14 * License along with this program; if not, write to the
15 * Free Software Foundation, Inc.,
16 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
21 #include "playermainwindow.h"
22 #include "ui_playermainwindow.h"
23 #include "configdialog.h"
24 #include "aboutdialog.h"
25 #include "accountdialog.h"
26 #include "appsettings.h"
27 #if defined(Q_WS_S60) || defined(Q_WS_MAEMO_5)
28 #include <QMaemo5InformationBox>
31 PlayerMainWindow::PlayerMainWindow(QWidget *parent) :
33 ui(new Ui::PlayerMainWindow)
36 setWindowTitle("Vlc remote");
38 mIsFirstStatusCall = true;
40 mTimer = new QTimer(this);
41 mNetManager = new QNetworkAccessManager(this);
42 mPlayListMainWindow = new PlayListMainWindow;
43 mBrowserMainWindow = new BrowseMainWindow;
44 mFavouritesMainWindow = new FavouritesMainWindow;
51 ui->playlistButton->setIcon(QIcon::fromTheme("notes_bullets"));
52 ui->browseButton->setIcon(QIcon::fromTheme("filemanager_media_folder"));
54 ui->previousButton->setIcon(QIcon::fromTheme("pdf_viewer_first_page"));
55 ui->nextButton->setIcon(QIcon::fromTheme("pdf_viewer_last_page"));
56 ui->playpauseButton->setIcon(QIcon::fromTheme("camera_playback"));
57 ui->stopButton->setIcon(QIcon::fromTheme("camera_video_stop"));
58 ui->fullscreenButton->setIcon(QIcon::fromTheme("general_fullsize"));
59 ui->volDown->setIcon(QIcon::fromTheme("statusarea_volumelevel1"));
60 ui->volUp->setIcon(QIcon::fromTheme("statusarea_volumelevel4"));
61 ui->volMute->setIcon(QIcon::fromTheme("statusarea_volume_mute"));
63 ui->labelArtPortrait->setVisible(false);
64 ui->labelArtLandscape->setVisible(false);
66 ui->labelTitle->setTextFormat(Qt::RichText);
67 ui->labelArtist->setTextFormat(Qt::RichText);
68 ui->labelAlbum->setTextFormat(Qt::RichText);
71 #if defined(Q_WS_S60) || defined(Q_WS_MAEMO_5)
73 mPlayListMainWindow->setParent(this);
74 mPlayListMainWindow->setAttribute(Qt::WA_Maemo5StackedWindow, true);
75 setAttribute(Qt::WA_Maemo5StackedWindow, true);
76 mPlayListMainWindow->setWindowFlags(mPlayListMainWindow->windowFlags() | Qt::Window);
78 mBrowserMainWindow->setParent(this);
79 mBrowserMainWindow->setAttribute(Qt::WA_Maemo5StackedWindow, true);
80 setAttribute(Qt::WA_Maemo5StackedWindow, true);
81 mBrowserMainWindow->setWindowFlags(mBrowserMainWindow->windowFlags() | Qt::Window);
83 mFavouritesMainWindow->setParent(this);
84 mFavouritesMainWindow->setAttribute(Qt::WA_Maemo5StackedWindow, true);
85 setAttribute(Qt::WA_Maemo5StackedWindow, true);
86 mFavouritesMainWindow->setWindowFlags(mFavouritesMainWindow->windowFlags() | Qt::Window);
88 connect(QApplication::desktop(), SIGNAL(resized(int)), this, SLOT(orientationChanged()));
92 connect(mTimer,SIGNAL(timeout()),this,SLOT(askStatus()));
93 connect(ui->actionConfiguration,SIGNAL(triggered()),this,SLOT(showConfig()));
94 connect(ui->actionAbout,SIGNAL(triggered()),this,SLOT(showAbout()));
95 connect(ui->actionPortrait,SIGNAL(triggered()),this,SLOT(setPortrait()));
96 connect(ui->actionLandscape,SIGNAL(triggered()),this,SLOT(setLandscape()));
97 connect(ui->actionAutoRotate,SIGNAL(triggered()),this,SLOT(setAutoRotate()));
98 connect(ui->playlistButton,SIGNAL(clicked()),mPlayListMainWindow,SLOT(show()));
99 connect(ui->playlistButton,SIGNAL(clicked()),mPlayListMainWindow,SLOT(showPlayList()));
100 connect(ui->browseButton,SIGNAL(clicked()),mBrowserMainWindow,SLOT(show()));
101 connect(ui->browseButton,SIGNAL(clicked()),mBrowserMainWindow,SLOT(showCurrentDirectory()));
103 connect(ui->playpauseButton,SIGNAL(clicked()),this,SLOT(playpause()));
104 connect(ui->stopButton,SIGNAL(clicked()),this,SLOT(stop()));
105 connect(ui->previousButton,SIGNAL(clicked()),this,SLOT(previous()));
106 connect(ui->nextButton,SIGNAL(clicked()),this,SLOT(next()));
107 connect(ui->fullscreenButton,SIGNAL(clicked()),this,SLOT(fullscreen()));
108 connect(ui->volUp,SIGNAL(clicked()),this,SLOT(volUp()));
109 connect(ui->volDown,SIGNAL(clicked()),this,SLOT(volDown()));
110 connect(ui->volMute,SIGNAL(clicked()),this,SLOT(volMute()));
111 connect(ui->slider,SIGNAL(sliderMoved(int)),this,SLOT(slide(int)));
113 connect(mPlayListMainWindow, SIGNAL(idUpdated(int,bool,QString)), this, SLOT(playlistIdUpdated(int, bool, QString)));
114 connect(mBrowserMainWindow, SIGNAL(showFavouritesWindow()), this, SLOT(showFavourites()));
116 connect(mFavouritesMainWindow, SIGNAL(browseDirectory(QString)), mFavouritesMainWindow, SLOT(close()));
117 connect(mFavouritesMainWindow, SIGNAL(browseDirectory(QString)), mBrowserMainWindow, SLOT(browseDirectory(QString)));
118 connect(mFavouritesMainWindow, SIGNAL(closeSignal()), mBrowserMainWindow, SLOT(show()));
121 // check if last used connection is still valid or showConfig
124 if (AppSettings::isConnected()) {
126 QString last_ip = AppSettings::getCurrentIp(); // AccountDialog::currentIp();
127 if (!last_ip.isNull() && !last_ip.isEmpty()) {
128 QTcpSocket * socket = new QTcpSocket;
129 if(last_ip.contains(":"))
131 QStringList hostSplit = last_ip.split(":");
132 QString ip = hostSplit.at(0);
133 QString port = hostSplit.at(1);
134 socket->connectToHost(ip,port.toInt());
137 socket->connectToHost(last_ip,8080);
139 if (!socket->waitForConnected(AppSettings::getConnectionTimeout())) {
145 mPlayListMainWindow->init();
146 mBrowserMainWindow->init();
147 mTimer->start(AppSettings::getStatusPollTimeout());
157 #if defined(Q_WS_S60) || defined(Q_WS_MAEMO_5)
158 QMaemo5InformationBox::information(this, tr("Network unavailable!"), QMaemo5InformationBox::DefaultTimeout);
165 PlayerMainWindow::~PlayerMainWindow()
170 void PlayerMainWindow::changeEvent(QEvent *e)
172 QMainWindow::changeEvent(e);
174 case QEvent::LanguageChange:
175 ui->retranslateUi(this);
182 void PlayerMainWindow::showFavourites() {
183 mFavouritesMainWindow->show();
184 mFavouritesMainWindow->init();
187 void PlayerMainWindow::setPortrait()
189 #if defined(Q_WS_S60) || defined(Q_WS_MAEMO_5)
190 AppSettings::setOrientation(PORTRAIT);
191 this->setAttribute(Qt::WA_Maemo5PortraitOrientation, true);
195 void PlayerMainWindow::setLandscape()
197 #if defined(Q_WS_S60) || defined(Q_WS_MAEMO_5)
198 AppSettings::setOrientation(LANDSCAPE);
199 this->setAttribute(Qt::WA_Maemo5LandscapeOrientation, true);
203 void PlayerMainWindow::setAutoRotate()
205 #if defined(Q_WS_S60) || defined(Q_WS_MAEMO_5)
206 AppSettings::setOrientation(AUTO_ROTATE);
207 this->setAttribute(Qt::WA_Maemo5AutoOrientation, true);
211 void PlayerMainWindow::orientationChanged() {
212 QRect screenGeometry = QApplication::desktop()->screenGeometry();
213 mIsLandscape = (screenGeometry.width() > screenGeometry.height());
216 ui->labelArtPortrait->setVisible(false);
217 ui->labelArtLandscape->setVisible(true);
220 ui->labelArtLandscape->setVisible(false);
221 ui->labelArtPortrait->setVisible(true);
225 ui->labelArtLandscape->setVisible(false);
226 ui->labelArtPortrait->setVisible(false);
230 void PlayerMainWindow::playpause()
232 // NB. There is no guarentee that our current state is the real current state.
233 // This is due to the polling frequency and possibility of user interaction directly on the server.
234 // Still this is probably better than nothing and our next real poll will set us straight again.
235 if (PAUSED == mCurrentStatus.state) {
236 mCurrentStatus.state = PLAYING;
238 updateUiWithCurrentStatus();
240 else if (PLAYING == mCurrentStatus.state) {
241 mCurrentStatus.state = PAUSED;
243 updateUiWithCurrentStatus();
246 // could be STOP or UNKNOWN, either way there is no guarentee we will enter a playing state next.
247 // So don't update the current state or UI
248 // Ideally we would try to find a way to check the current state again but this could lead to an infinite loop!
252 void PlayerMainWindow::play()
254 mNetManager->get(QNetworkRequest(QUrl("http://"+mIp+"/requests/status.xml?command=pl_play")));
256 void PlayerMainWindow::stop()
258 mNetManager->get(QNetworkRequest(QUrl("http://"+mIp+"/requests/status.xml?command=pl_stop")));
260 void PlayerMainWindow::pause()
262 mNetManager->get(QNetworkRequest(QUrl("http://"+mIp+"/requests/status.xml?command=pl_pause")));
264 void PlayerMainWindow::previous()
266 mNetManager->get(QNetworkRequest(QUrl("http://"+mIp+"/requests/status.xml?command=pl_previous")));
268 void PlayerMainWindow::next()
270 mNetManager->get(QNetworkRequest(QUrl("http://"+mIp+"/requests/status.xml?command=pl_next")));
272 void PlayerMainWindow::fullscreen()
274 mNetManager->get(QNetworkRequest(QUrl("http://"+mIp+"/requests/status.xml?command=fullscreen")));
276 void PlayerMainWindow::volUp()
278 QUrl url = QUrl("http://"+mIp+"/requests/status.xml?command=volume");
279 url.addEncodedQueryItem(QByteArray("val"), QByteArray("%2B10"));
280 mNetManager->get(QNetworkRequest(url));
282 void PlayerMainWindow::volDown()
284 mNetManager->get(QNetworkRequest(QUrl("http://"+mIp+"/requests/status.xml?command=volume&val=-10")));
286 void PlayerMainWindow::volMute()
288 this->mMuted = !this->mMuted;
290 mNetManager->get(QNetworkRequest(QUrl("http://"+mIp+"/requests/status.xml?command=volume&val=0")));
293 mNetManager->get(QNetworkRequest(QUrl("http://"+mIp+"/requests/status.xml?command=volume&val="+QString::number(this->mVolume))));
296 void PlayerMainWindow::slide(int value)
298 mNetManager->get(QNetworkRequest(QUrl("http://"+mIp+"/requests/status.xml?command=seek&val="+QString::number(value)+"%25")));
301 void PlayerMainWindow::showConfig()
305 if (AppSettings::isConnected()) {
306 AccountDialog * dialog = new AccountDialog(this);
309 mIp= AppSettings::getCurrentIp(); //AccountDialog::currentIp();
311 mPlayListMainWindow->init();
312 mBrowserMainWindow->init();
313 mTimer->start(AppSettings::getStatusPollTimeout());
317 #if defined(Q_WS_S60) || defined(Q_WS_MAEMO_5)
318 QMaemo5InformationBox::information(this, tr("Network unavailable!"), QMaemo5InformationBox::DefaultTimeout);
320 QTimer::singleShot(AppSettings::getRetryNetworkTimeout(), this, SLOT(showConfig()));
323 void PlayerMainWindow::showAbout()
326 AboutDialog * dialog = new AboutDialog;
331 void PlayerMainWindow::askStatus()
333 //qDebug() << "Status requested. at:" << QTime::currentTime().toString("hh::mm:ss");
334 if (AppSettings::isConnected()) {
335 QNetworkReply * reply = mNetManager->get(QNetworkRequest(QUrl("http://"+mIp+"/requests/status.xml")));
336 connect(reply,SIGNAL(readyRead()),this,SLOT(parseXmlStatus()));
337 connect(reply,SIGNAL(error(QNetworkReply::NetworkError)),this,SLOT(error(QNetworkReply::NetworkError)));
340 showConfig(); // this will handle stopping and restarting the timer.
344 void PlayerMainWindow::parseXmlStatus()
346 QNetworkReply * reply = qobject_cast<QNetworkReply*>(sender());
348 doc.setContent(reply->readAll());
350 QDomElement docElem = doc.documentElement();
351 VlcStatusState oldState = mCurrentStatus.state;
352 // Get the raw values
353 int volume = docElem.namedItem("volume").toElement().text().toInt();
354 int length = docElem.namedItem("length").toElement().text().toInt();
355 int time = docElem.namedItem("time").toElement().text().toInt();
356 int position = docElem.namedItem("position").toElement().text().toInt();
357 int random = docElem.namedItem("random").toElement().text().toInt();
358 int loop = docElem.namedItem("loop").toElement().text().toInt();
359 int repeat = docElem.namedItem("repeat").toElement().text().toInt();
360 QString state = docElem.namedItem("state").toElement().text();
361 QDomNode infoNode = docElem.namedItem("information");
362 QDomNode metaInfoNode = infoNode.namedItem("meta-information");
363 QString title = metaInfoNode.namedItem("title").toElement().text().replace("\\\\", "\\");
364 // if it's a file style title fix it up
365 if (40 < title.length()) {
366 if (0 < title.lastIndexOf("\\")) {
367 title = title.right(title.length() - (title.lastIndexOf("\\") + 1));
369 else if (0 < title.lastIndexOf("/")) {
370 title = title.right(title.length() - (title.lastIndexOf("/") + 1));
373 QString artist = metaInfoNode.namedItem("artist").toElement().text();
374 QString album = metaInfoNode.namedItem("album").toElement().text();
375 QString now_playing = metaInfoNode.namedItem("now_playing").toElement().text();
376 QString art_url = metaInfoNode.namedItem("art_url").toElement().text();
377 // Populate the current status structure
378 // now would be a good time to work out if we are a new track / file or not.
379 // key if we are going to look for album art later
380 // for now we check length and title this will require further examination later
381 if (!state.isNull() && !state.isEmpty()) {
382 if (0 == QString::compare("playing", state, Qt::CaseInsensitive)) {
383 mCurrentStatus.state = PLAYING;
385 else if (0 == QString::compare("paused", state, Qt::CaseInsensitive)) {
386 mCurrentStatus.state = PAUSED;
388 else if (0 == QString::compare("stop", state, Qt::CaseInsensitive)) {
389 mCurrentStatus.state = STOP;
392 mCurrentStatus.state = UNKNOWN;
393 mIsFirstStatusCall = true;
397 mCurrentStatus.state = UNKNOWN;
398 mIsFirstStatusCall = true;
400 mCurrentStatus.newtrack = true;
401 if (mIsFirstStatusCall) {
402 mIsFirstStatusCall = false;
403 mCurrentStatus.newtrack = true; // unneeded but self-documenting
405 else if (STOP == oldState && STOP != mCurrentStatus.state) {
406 mCurrentStatus.newtrack = true; // unneeded but self-documenting
409 if ( (0 == mCurrentStatus.length || STOP == mCurrentStatus.state) // stopped or null
410 || // same track as current playing
411 (mCurrentStatus.length == length && !mCurrentStatus.title.isNull() && !title.isNull() && 0 == QString::compare(mCurrentStatus.title, title)) ){
412 mCurrentStatus.newtrack = false;
415 mCurrentStatus.volume = volume;
416 mCurrentStatus.length = length;
417 mCurrentStatus.time = time;
418 mCurrentStatus.position = position;
419 mCurrentStatus.random = (1 == random);
420 mCurrentStatus.loop = (1 == loop);
421 mCurrentStatus.repeat = (1 == repeat);
422 mCurrentStatus.title = title;
423 mCurrentStatus.artist = artist;
424 mCurrentStatus.album = album;
425 mCurrentStatus.nowplaying = now_playing;
426 mCurrentStatus.hasart = (!art_url.isNull() && !art_url.isEmpty());
427 // What's our mute status?
428 if (0 < mCurrentStatus.volume) {
429 this->mVolume = mCurrentStatus.volume;
430 this->mMuted = false;
436 updateUiWithCurrentStatus();
440 void PlayerMainWindow::updateUiWithCurrentStatus() {
442 QTime timePosition(0,0,0) ;
443 timePosition = timePosition.addSecs(mCurrentStatus.time);
445 ui->timeLabel->setText(timePosition.toString("h:mm:ss"));
448 if (0 < mCurrentStatus.length) {
449 QTime timeDuration(0,0,0) ;
450 timeDuration = timeDuration.addSecs(mCurrentStatus.length);
452 ui->durationLabel->setText(timeDuration.toString("h:mm:ss"));
455 ui->durationLabel->setText("0:00:00");
459 if (mCurrentStatus.position >= 0 && mCurrentStatus.position <= 100) {
460 ui->slider->blockSignals(true);
461 ui->slider->setValue(mCurrentStatus.position);
462 ui->slider->blockSignals(false);
465 ui->labelTitle->setText(mCurrentStatus.title);
466 ui->labelArtist->setText(mCurrentStatus.artist);
467 ui->labelAlbum->setText(mCurrentStatus.album);
469 if (PLAYING == mCurrentStatus.state) {
470 ui->playpauseButton->setIcon(QIcon::fromTheme("camera_video_pause"));
473 ui->playpauseButton->setIcon(QIcon::fromTheme("camera_playback"));
476 if (STOP == mCurrentStatus.state) {
477 ui->labelArtPortrait->setVisible(false);
478 ui->labelArtLandscape->setVisible(false);
481 if (mCurrentStatus.newtrack && STOP != mCurrentStatus.state) {
482 // potential actions:
483 // rebuild display layout
484 // retrieve album art
486 QTimer::singleShot(AppSettings::getRetrieveArtTimeout(), mPlayListMainWindow, SLOT(requestPlayList()));
488 // Update the buttons on the playlist window
489 if (NULL != this->mPlayListMainWindow) {
490 this->mPlayListMainWindow->updateUiWithCurrentStatus(& mCurrentStatus);
494 void PlayerMainWindow::playlistIdUpdated(int id, bool hasart, QString extension) {
500 ui->labelArtLandscape->setVisible(false);
501 ui->labelArtPortrait->setVisible(false);
502 // could use a default graphic from extension here!
503 // setCoverArtFromPixmap();
506 void PlayerMainWindow::error(QNetworkReply::NetworkError code) {
507 qDebug() << "Error Code: " << code;
509 void PlayerMainWindow::readReady() {
510 QNetworkReply * reply = qobject_cast<QNetworkReply*>(sender());
512 mResponse += reply->readAll();
514 void PlayerMainWindow::finished(QNetworkReply * reply) {
515 // now we can call setCoverArt to process the full buffers
516 this->setCoverArt(mResponse);
517 // only interested in finished signals
518 disconnect(mNetManager,SIGNAL(finished(QNetworkReply *)),this,SLOT(finished(QNetworkReply *)));
521 void PlayerMainWindow::getCoverArt(int id) {
522 qDebug() << "getCoverArt id=!" << id;
524 QNetworkReply * reply = mNetManager->get(QNetworkRequest(QUrl("http://"+mIp+"/art?id=" + QString::number(id))));
525 connect(reply,SIGNAL(readyRead()),this,SLOT(readReady()));
526 connect(reply,SIGNAL(error(QNetworkReply::NetworkError)),this,SLOT(error(QNetworkReply::NetworkError)));
527 connect(mNetManager,SIGNAL(finished(QNetworkReply *)),this,SLOT(finished(QNetworkReply *)));
530 void PlayerMainWindow::setCoverArt(const QByteArray data) {
531 QPixmap* image = new QPixmap();
532 if (image->loadFromData(data)) {
534 ui->labelArtLandscape->setPixmap(image->scaledToHeight(120, Qt::SmoothTransformation));
535 ui->labelArtPortrait->setPixmap(image->scaledToHeight(310, Qt::SmoothTransformation));
537 ui->labelArtPortrait->setVisible(false);
538 ui->labelArtLandscape->setVisible(true);
541 ui->labelArtLandscape->setVisible(false);
542 ui->labelArtPortrait->setVisible(true);
546 qDebug() << "image load failed!";
547 qDebug() << "data.length" << data.length();
548 ui->labelArtPortrait->setVisible(false);
549 ui->labelArtLandscape->setVisible(false);
553 void PlayerMainWindow::setCoverArtFromPixmap(QPixmap image) {
555 ui->labelArtLandscape->setPixmap(image.scaledToHeight(120, Qt::SmoothTransformation));
556 ui->labelArtPortrait->setPixmap(image.scaledToHeight(320, Qt::SmoothTransformation));
558 ui->labelArtPortrait->setVisible(false);
559 ui->labelArtLandscape->setVisible(true);
562 ui->labelArtLandscape->setVisible(false);
563 ui->labelArtPortrait->setVisible(true);