Misc. changes.
[wallplayer.git] / src / qmpwidget.cpp
blob598b8ff1e2cee76e6a7ad5a34331305bd3e6096a
1 /*
2 * qmpwidget - A Qt widget for embedding MPlayer
3 * Copyright (C) 2010 by Jonas Gehring
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
20 //#define QMP_DEBUG_OUTPUT 1
22 #include <iostream>
23 #include <QAbstractSlider>
24 #include <QKeyEvent>
25 #include <QLocalSocket>
26 #include <QPainter>
27 #include <QProcess>
28 #include <QStringList>
29 #include <QTemporaryFile>
30 #include <QThread>
31 #include <QtDebug>
33 #ifdef QT_OPENGL_LIB
34 #include <QGLWidget>
35 #endif
37 #include "qmpwidget.h"
39 #define QMP_DEBUG_OUTPUT
41 #ifdef QMP_USE_YUVPIPE
42 #include "qmpyuvreader.h"
43 #endif // QMP_USE_YUVPIPE
46 // A plain video widget
47 class QMPPlainVideoWidget : public QWidget
49 Q_OBJECT
51 public:
52 QMPPlainVideoWidget(QWidget *parent = 0)
53 : QWidget(parent)
55 setAttribute(Qt::WA_NoSystemBackground);
56 setMouseTracking(true);
59 void showUserImage(const QImage &image)
61 m_userImage = image;
62 update();
65 public slots:
66 void displayImage(const QImage &image)
68 m_pixmap = QPixmap::fromImage(image);
69 update();
72 protected:
73 void paintEvent(QPaintEvent *event)
75 Q_UNUSED(event);
76 QPainter p(this);
77 p.setCompositionMode(QPainter::CompositionMode_Source);
79 if (!m_userImage.isNull()) {
80 p.fillRect(rect(), Qt::black);
81 p.drawImage(rect().center() - m_userImage.rect().center(), m_userImage);
82 } else if (!m_pixmap.isNull()) {
83 p.drawPixmap(rect(), m_pixmap);
84 } else {
85 p.fillRect(rect(), Qt::black);
87 p.end();
90 private:
91 QPixmap m_pixmap;
92 QImage m_userImage;
96 #ifdef QT_OPENGL_LIB
98 // A OpenGL video widget
99 class QMPOpenGLVideoWidget : public QGLWidget
101 Q_OBJECT
103 public:
104 QMPOpenGLVideoWidget(QWidget *parent = 0)
105 : QGLWidget(parent), m_tex(-1)
107 setMouseTracking(true);
110 void showUserImage(const QImage &image)
112 m_userImage = image;
114 makeCurrent();
115 if (m_tex >= 0) {
116 deleteTexture(m_tex);
118 if (!m_userImage.isNull()) {
119 m_tex = bindTexture(image);
120 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
121 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
122 } else {
123 glViewport(0, 0, width(), qMax(height(), 1));
125 updateGL();
128 public slots:
129 void displayImage(const QImage &image)
131 if (!m_userImage.isNull()) {
132 return;
135 makeCurrent();
136 if (m_tex >= 0) {
137 deleteTexture(m_tex);
139 m_tex = bindTexture(image);
140 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
141 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
142 updateGL();
145 protected:
146 void initializeGL()
148 glEnable(GL_TEXTURE_2D);
149 glClearColor(0, 0, 0, 0);
150 glClearDepth(1);
153 void resizeGL(int w, int h)
155 glViewport(0, 0, w, qMax(h, 1));
158 void paintGL()
160 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
161 glLoadIdentity();
162 if (m_tex >= 0) {
163 glBindTexture(GL_TEXTURE_2D, m_tex);
164 if (!m_userImage.isNull()) {
165 QRect r = m_userImage.rect();
166 r.moveTopLeft(rect().center() - m_userImage.rect().center());
167 glViewport(r.x(), r.y(), r.width(), r.height());
169 glBegin(GL_QUADS);
170 glTexCoord2f(0, 0); glVertex2f(-1, -1);
171 glTexCoord2f(1, 0); glVertex2f( 1, -1);
172 glTexCoord2f(1, 1); glVertex2f( 1, 1);
173 glTexCoord2f(0, 1); glVertex2f(-1, 1);
174 glEnd();
178 private:
179 QImage m_userImage;
180 int m_tex;
183 #endif // QT_OPENGL_LIB
186 // A custom QProcess designed for the MPlayer slave interface
187 class QMPProcess : public QProcess
189 Q_OBJECT
191 public:
192 QMPProcess(QObject *parent = 0)
193 : QProcess(parent), m_state(QMPwidget::NotStartedState), m_mplayerPath("mplayer"),
194 m_fakeInputconf(NULL)
195 #ifdef QMP_USE_YUVPIPE
196 , m_yuvReader(NULL)
197 #endif
199 resetValues();
201 #ifdef Q_WS_WIN
202 m_mode = QMPwidget::EmbeddedMode;
203 m_videoOutput = "directx,directx:noaccel";
204 #elif defined(Q_WS_X11)
205 m_mode = QMPwidget::EmbeddedMode;
206 #ifdef QT_OPENGL_LIB
207 m_videoOutput = "gl2,gl,xv";
208 #else
209 m_videoOutput = "xv";
210 #endif
211 #elif defined(Q_WS_MAC)
212 m_mode = QMPwidget::PipeMode;
213 #ifdef QT_OPENGL_LIB
214 m_videoOutput = "gl,quartz";
215 #else
216 m_videoOutput = "quartz";
217 #endif
218 #endif
220 m_movieFinishedTimer.setSingleShot(true);
221 m_movieFinishedTimer.setInterval(100);
223 connect(this, SIGNAL(readyReadStandardOutput()), this, SLOT(readStdout()));
224 connect(this, SIGNAL(readyReadStandardError()), this, SLOT(readStderr()));
225 connect(this, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(finished()));
226 connect(&m_movieFinishedTimer, SIGNAL(timeout()), this, SLOT(movieFinished()));
229 ~QMPProcess()
231 #ifdef QMP_USE_YUVPIPE
232 if (m_yuvReader != NULL) {
233 m_yuvReader->stop();
235 #endif
236 if (m_fakeInputconf != NULL) {
237 delete m_fakeInputconf;
241 // Starts the MPlayer process in idle mode
242 void start(QWidget *widget, const QStringList &args)
244 if (m_mode == QMPwidget::PipeMode) {
245 #ifdef QMP_USE_YUVPIPE
246 m_yuvReader = new QMPYuvReader(this);
247 #else
248 m_mode = QMPwidget::EmbeddedMode;
249 #endif
252 // Figure out the mplayer version in order to check if
253 // "-input nodefault-bindings" is available
254 bool useFakeInputconf = true;
255 QString version = mplayerVersion();
256 if (version.contains("SVN")) { // Check revision
257 QRegExp re("SVN-r([0-9]*)");
258 if (re.indexIn(version) > -1) {
259 int revision = re.cap(1).toInt();
260 if (revision >= 28878) {
261 useFakeInputconf = false;
266 QStringList myargs;
267 myargs += "-slave";
268 myargs += "-idle";
269 myargs += "-noquiet";
270 myargs += "-identify";
271 myargs += "-nomouseinput";
272 myargs += "-nokeepaspect";
273 myargs += "-monitorpixelaspect";
274 myargs += "1";
275 if (!useFakeInputconf) {
276 myargs += "-input";
277 myargs += "nodefault-bindings:conf=/dev/null";
278 } else {
279 #ifndef Q_WS_WIN
280 // Ugly hack for older versions of mplayer (used in kmplayer and other)
281 if (m_fakeInputconf == NULL) {
282 m_fakeInputconf = new QTemporaryFile();
283 if (m_fakeInputconf->open()) {
284 writeFakeInputconf(m_fakeInputconf);
285 } else {
286 delete m_fakeInputconf;
287 m_fakeInputconf = NULL;
290 if (m_fakeInputconf != NULL) {
291 myargs += "-input";
292 myargs += QString("conf=%1").arg(m_fakeInputconf->fileName());
294 #endif
297 if (m_mode == QMPwidget::EmbeddedMode) {
298 myargs += "-wid";
299 myargs += QString::number((int)widget->winId());
300 if (!m_videoOutput.isEmpty()) {
301 myargs += "-vo";
302 myargs += m_videoOutput;
304 } else {
305 #ifdef QMP_USE_YUVPIPE
306 myargs += "-vo";
307 myargs += QString("yuv4mpeg:file=%1").arg(m_yuvReader->m_pipe);
308 #endif
311 myargs += args;
312 #ifdef QMP_DEBUG_OUTPUT
313 qDebug() << myargs;
314 #endif
315 QProcess::start(m_mplayerPath, myargs);
316 changeState(QMPwidget::IdleState);
318 if (m_mode == QMPwidget::PipeMode) {
319 #ifdef QMP_USE_YUVPIPE
320 connect(m_yuvReader, SIGNAL(imageReady(const QImage &)), widget, SLOT(displayImage(const QImage &)));
321 m_yuvReader->start();
322 #endif
326 QString mplayerVersion()
328 QProcess p;
329 p.start(m_mplayerPath, QStringList("-version"));
330 if (!p.waitForStarted()) {
331 return QString();
333 if (!p.waitForFinished()) {
334 return QString();
337 QString output = QString(p.readAll());
338 QRegExp re("MPlayer ([^ ]*)");
339 if (re.indexIn(output) > -1) {
340 return re.cap(1);
342 return output;
345 QProcess::ProcessState processState() const
347 return QProcess::state();
350 void writeCommand(const QString &command)
352 #ifdef QMP_DEBUG_OUTPUT
353 qDebug("in: \"%s\"", qPrintable(command));
354 #endif
355 QProcess::write(command.toLocal8Bit()+"\n");
358 void quit()
360 writeCommand("quit");
361 QProcess::waitForFinished(100);
362 if (QProcess::state() == QProcess::Running) {
363 QProcess::kill();
365 QProcess::waitForFinished(-1);
368 void pause()
370 writeCommand("pause");
373 void stop()
375 changeState(QMPwidget::StoppedState);
376 writeCommand("stop");
379 signals:
380 void stateChanged(int state);
381 void streamPositionChanged(double position);
382 void error(const QString &reason);
384 void readStandardOutput(const QString &line);
385 void readStandardError(const QString &line);
387 private slots:
388 void readStdout()
390 QStringList lines = QString::fromLocal8Bit(readAllStandardOutput()).split("\n", QString::SkipEmptyParts);
391 for (int i = 0; i < lines.count(); i++) {
392 lines[i].remove("\r");
393 #ifdef QMP_DEBUG_OUTPUT
394 qDebug("out: \"%s\"", qPrintable(lines[i]));
395 #endif
396 parseLine(lines[i]);
397 emit readStandardOutput(lines[i]);
401 void readStderr()
403 QStringList lines = QString::fromLocal8Bit(readAllStandardError()).split("\n", QString::SkipEmptyParts);
404 for (int i = 0; i < lines.count(); i++) {
405 lines[i].remove("\r");
406 #ifdef QMP_DEBUG_OUTPUT
407 qDebug("err: \"%s\"", qPrintable(lines[i]));
408 #endif
409 parseLine(lines[i]);
410 emit readStandardError(lines[i]);
414 void finished()
416 // Called if the *process* has finished
417 changeState(QMPwidget::NotStartedState);
420 void movieFinished()
422 if (m_state == QMPwidget::PlayingState) {
423 changeState(QMPwidget::IdleState);
427 private:
428 // Parses a line of MPlayer output
429 void parseLine(const QString &line)
431 if (line.startsWith("Playing ")) {
432 changeState(QMPwidget::LoadingState);
433 } else if (line.startsWith("Cache fill:")) {
434 changeState(QMPwidget::BufferingState);
435 } else if (line.startsWith("Starting playback...")) {
436 m_mediaInfo.ok = true; // No more info here
437 changeState(QMPwidget::PlayingState);
438 } else if (line.startsWith("File not found: ")) {
439 changeState(QMPwidget::ErrorState);
440 } else if (line.endsWith("ID_PAUSED")) {
441 changeState(QMPwidget::PausedState);
442 } else if (line.startsWith("ID_")) {
443 parseMediaInfo(line);
444 } else if (line.startsWith("No stream found")) {
445 changeState(QMPwidget::ErrorState, line);
446 } else if (line.startsWith("A:") || line.startsWith("V:")) {
447 if (m_state != QMPwidget::PlayingState
448 && m_state != QMPwidget::StoppedState) {
449 changeState(QMPwidget::PlayingState);
451 parsePosition(line);
452 } else if (line.startsWith("Exiting...")) {
453 changeState(QMPwidget::NotStartedState);
457 // Parses MPlayer's media identification output
458 void parseMediaInfo(const QString &line)
460 QStringList info = line.split("=");
461 if (info.count() < 2) {
462 return;
465 if (info[0] == "ID_VIDEO_FORMAT") {
466 m_mediaInfo.videoFormat = info[1];
467 } else if (info[0] == "ID_VIDEO_BITRATE") {
468 m_mediaInfo.videoBitrate = info[1].toInt();
469 } else if (info[0] == "ID_VIDEO_WIDTH") {
470 m_mediaInfo.size.setWidth(info[1].toInt());
471 } else if (info[0] == "ID_VIDEO_HEIGHT") {
472 m_mediaInfo.size.setHeight(info[1].toInt());
473 } else if (info[0] == "ID_VIDEO_FPS") {
474 m_mediaInfo.framesPerSecond = info[1].toDouble();
476 } else if (info[0] == "ID_AUDIO_FORMAT") {
477 m_mediaInfo.audioFormat = info[1];
478 } else if (info[0] == "ID_AUDIO_BITRATE") {
479 m_mediaInfo.audioBitrate = info[1].toInt();
480 } else if (info[0] == "ID_AUDIO_RATE") {
481 m_mediaInfo.sampleRate = info[1].toInt();
482 } else if (info[0] == "ID_AUDIO_NCH") {
483 m_mediaInfo.numChannels = info[1].toInt();
485 } else if (info[0] == "ID_LENGTH") {
486 m_mediaInfo.length = info[1].toDouble();
487 } else if (info[0] == "ID_SEEKABLE") {
488 m_mediaInfo.seekable = (bool)info[1].toInt();
490 } else if (info[0].startsWith("ID_CLIP_INFO_NAME")) {
491 m_currentTag = info[1];
492 } else if (info[0].startsWith("ID_CLIP_INFO_VALUE") && !m_currentTag.isEmpty()) {
493 m_mediaInfo.tags.insert(m_currentTag, info[1]);
497 // Parsas MPlayer's position output
498 void parsePosition(const QString &line)
500 static QRegExp rx("[ :]");
501 QStringList info = line.split(rx, QString::SkipEmptyParts);
503 double oldpos = m_streamPosition;
504 for (int i = 0; i < info.count(); i++) {
505 if (info[i] == "V" && info.count() > i) {
506 m_streamPosition = info[i+1].toDouble();
508 // If the movie is near its end, start a timer that will check whether
509 // the movie has really finished.
510 if (qAbs(m_streamPosition - m_mediaInfo.length) < 1) {
511 m_movieFinishedTimer.start();
516 if (oldpos != m_streamPosition) {
517 emit streamPositionChanged(m_streamPosition);
521 // Changes the current state, possibly emitting multiple signals
522 void changeState(QMPwidget::State state, const QString &comment = QString())
524 #ifdef QMP_USE_YUVPIPE
525 if (m_yuvReader != NULL && (state == QMPwidget::ErrorState || state == QMPwidget::NotStartedState)) {
526 m_yuvReader->stop();
527 m_yuvReader->deleteLater();
529 #endif
531 if (m_state == state) {
532 return;
535 if (m_state == QMPwidget::PlayingState) {
536 m_movieFinishedTimer.stop();
539 m_state = state;
540 emit stateChanged(m_state);
542 switch (m_state) {
543 case QMPwidget::NotStartedState:
544 resetValues();
545 break;
547 case QMPwidget::ErrorState:
548 emit error(comment);
549 resetValues();
550 break;
552 default: break;
556 // Resets the media info and position values
557 void resetValues()
559 m_mediaInfo = QMPwidget::MediaInfo();
560 m_streamPosition = -1;
563 // Writes a dummy input configuration to the given device
564 void writeFakeInputconf(QIODevice *device)
566 // Query list of supported keys
567 QProcess p;
568 p.start(m_mplayerPath, QStringList("-input") += "keylist");
569 if (!p.waitForStarted()) {
570 return;
572 if (!p.waitForFinished()) {
573 return;
575 QStringList keys = QString(p.readAll()).split("\n", QString::SkipEmptyParts);
577 // Write dummy command for each key
578 QTextStream out(device);
579 for (int i = 0; i < keys.count(); i++) {
580 keys[i].remove("\r");
581 out << keys[i] << " " << "ignored" << endl;
585 public:
586 QMPwidget::State m_state;
588 QString m_mplayerPath;
589 QString m_videoOutput;
590 QString m_pipe;
591 QMPwidget::Mode m_mode;
593 QMPwidget::MediaInfo m_mediaInfo;
594 double m_streamPosition; // This is the video position
595 QTimer m_movieFinishedTimer;
597 QString m_currentTag;
599 QTemporaryFile *m_fakeInputconf;
601 #ifdef QMP_USE_YUVPIPE
602 QPointer<QMPYuvReader> m_yuvReader;
603 #endif
607 // Initialize the media info structure
608 QMPwidget::MediaInfo::MediaInfo()
609 : videoBitrate(0), framesPerSecond(0), sampleRate(0), numChannels(0),
610 ok(false), length(0), seekable(false)
617 * \brief Constructor
619 * \param parent Parent widget
621 QMPwidget::QMPwidget(QWidget *parent)
622 : QWidget(parent)
624 setFocusPolicy(Qt::StrongFocus);
625 setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
627 #ifdef QT_OPENGL_LIB
628 m_widget = new QMPOpenGLVideoWidget(this);
629 #else
630 m_widget = new QMPPlainVideoWidget(this);
631 #endif
633 QPalette p = palette();
634 p.setColor(QPalette::Window, Qt::black);
635 setPalette(p);
637 m_seekTimer.setInterval(50);
638 m_seekTimer.setSingleShot(true);
639 connect(&m_seekTimer, SIGNAL(timeout()), this, SLOT(delayedSeek()));
641 m_process = new QMPProcess(this);
642 connect(m_process, SIGNAL(stateChanged(int)), this, SLOT(mpStateChanged(int)));
643 connect(m_process, SIGNAL(streamPositionChanged(double)), this, SLOT(mpStreamPositionChanged(double)));
644 connect(m_process, SIGNAL(error(const QString &)), this, SIGNAL(error(const QString &)));
645 connect(m_process, SIGNAL(readStandardOutput(const QString &)), this, SIGNAL(readStandardOutput(const QString &)));
646 connect(m_process, SIGNAL(readStandardError(const QString &)), this, SIGNAL(readStandardError(const QString &)));
650 * \brief Destructor
651 * \details
652 * This function will ask the MPlayer process to quit and block until it has really
653 * finished.
655 QMPwidget::~QMPwidget()
657 if (m_process->processState() == QProcess::Running) {
658 m_process->quit();
660 delete m_process;
664 * \brief Returns the current MPlayer process state
666 * \returns The process state
668 QMPwidget::State QMPwidget::state() const
670 return m_process->m_state;
674 * \brief Returns the current media info object
675 * \details
676 * Please check QMPwidget::MediaInfo::ok to make sure the media
677 * information has been fully parsed.
679 * \returns The media info object
681 QMPwidget::MediaInfo QMPwidget::mediaInfo() const
683 return m_process->m_mediaInfo;
687 * \brief Returns the current playback position
689 * \returns The current playback position in seconds
690 * \sa seek()
692 double QMPwidget::tell() const
694 return m_process->m_streamPosition;
698 * \brief Returns the MPlayer process
700 * \returns The MPlayer process
702 QProcess *QMPwidget::process() const
704 return m_process;
708 * \brief Sets the video playback mode
709 * \details
710 * Please see \ref playbackmodes for a discussion of the available modes.
712 * \param mode The video playback mode
713 * \sa mode()
715 void QMPwidget::setMode(Mode mode)
717 #ifdef QMP_USE_YUVPIPE
718 m_process->m_mode = mode;
719 #else
720 Q_UNUSED(mode)
721 #endif
725 * \brief Returns the current video playback mode
727 * \returns The current video playback mode
728 * \sa setMode()
730 QMPwidget::Mode QMPwidget::mode() const
732 return m_process->m_mode;
736 * \brief Sets the video output mode
737 * \details
738 * The video output mode string will be passed to MPlayer using its \p -vo option.
739 * Please see http://www.mplayerhq.hu/DOCS/HTML/en/video.html for an overview of
740 * available video output modes.
742 * Per default, this string will have the following values:
743 * <table>
744 * <tr><th>System</th><th>Configuration</th><th>Value</th></tr>
745 * <tr>
746 * <td>Windows</td>
747 * <td></td>
748 * <td>\p "directx,directx:noaccel"</td>
749 * </tr>
750 * <tr>
751 * <td>X11</td>
752 * <td>Compiled without OpenGL support</td>
753 * <td>\p "xv"</td>
754 * </tr>
755 * <tr>
756 * <td>X11</td>
757 * <td>Compiled with OpenGL support</td>
758 * <td>\p "gl2,gl,xv"</td>
759 * </tr>
760 * <tr>
761 * <td>Mac OS X</td>
762 * <td>Compiled without OpenGL support</td>
763 * <td>\p "quartz"</td>
764 * </tr>
765 * <tr>
766 * <td>Mac OS X</td>
767 * <td>Compiled with OpenGL support</td>
768 * <td>\p "gl,quartz"</td>
769 * </tr>
770 * </table>
773 * \param output The video output mode string
774 * \sa videoOutput()
776 void QMPwidget::setVideoOutput(const QString &output)
778 m_process->m_videoOutput = output;
782 * \brief Returns the current video output mode
784 * \returns The current video output mode
785 * \sa setVideoOutput()
787 QString QMPwidget::videoOutput() const
789 return m_process->m_videoOutput;
793 * \brief Sets the path to the MPlayer executable
794 * \details
795 * Per default, it is assumed the MPlayer executable is
796 * available in the current OS path. Therefore, this value is
797 * set to "mplayer".
799 * \param path Path to the MPlayer executable
800 * \sa mplayerPath()
802 void QMPwidget::setMPlayerPath(const QString &path)
804 m_process->m_mplayerPath = path;
808 * \brief Returns the current path to the MPlayer executable
810 * \returns The path to the MPlayer executable
811 * \sa setMPlayerPath()
813 QString QMPwidget::mplayerPath() const
815 return m_process->m_mplayerPath;
819 * \brief Returns the version string of the MPlayer executable
820 * \details
821 * If the mplayer
824 * \returns The version string of the MPlayer executable
826 QString QMPwidget::mplayerVersion()
828 return m_process->mplayerVersion();
832 * \brief Sets a seeking slider for this widget
834 void QMPwidget::setSeekSlider(QAbstractSlider *slider)
836 if (m_seekSlider) {
837 m_seekSlider->disconnect(this);
838 disconnect(m_seekSlider);
841 if (m_process->m_mediaInfo.ok) {
842 slider->setRange(0, m_process->m_mediaInfo.length);
844 if (m_process->m_mediaInfo.ok) {
845 slider->setEnabled(m_process->m_mediaInfo.seekable);
848 connect(slider, SIGNAL(valueChanged(int)), this, SLOT(seek(int)));
849 m_seekSlider = slider;
853 * \brief Sets a volume slider for this widget
855 void QMPwidget::setVolumeSlider(QAbstractSlider *slider)
857 if (m_volumeSlider) {
858 m_volumeSlider->disconnect(this);
859 disconnect(m_volumeSlider);
863 slider->setRange(0, 100);
864 slider->setValue(100); // TODO
866 connect(slider, SIGNAL(valueChanged(int)), this, SLOT(setVolume(int)));
867 m_volumeSlider = slider;
871 * \brief Shows a custom image
872 * \details
873 * This function sets a custom image that will be shown instead of the MPlayer
874 * video output. In order to show MPlayer's output again, call this function
875 * with a null image.
877 * \note If the current playback mode is not set to \p PipeMode, this function
878 * will have no effect if MPlayer draws to the widget.
880 * \param image Custom image
882 void QMPwidget::showImage(const QImage &image)
884 #ifdef QT_OPENGL_LIB
885 qobject_cast<QMPOpenGLVideoWidget *>(m_widget)->showUserImage(image);
886 #else
887 qobject_cast<QMPPlainVideoWidget*>(m_widget)->showUserImage(image);
888 #endif
892 * \brief Returns a suitable size hint for this widget
893 * \details
894 * This function is used internally by Qt.
896 QSize QMPwidget::sizeHint() const
898 if (m_process->m_mediaInfo.ok && !m_process->m_mediaInfo.size.isNull()) {
899 return m_process->m_mediaInfo.size;
901 return QWidget::sizeHint();
905 * \brief Starts the MPlayer process with the given arguments
906 * \details
907 * If there's another process running, it will be terminated first. MPlayer
908 * will be run in idle mode and is avaiting your commands, e.g. via load().
910 * \param args MPlayer command line arguments
912 void QMPwidget::start(const QStringList &args)
914 if (m_process->processState() == QProcess::Running) {
915 m_process->quit();
917 m_process->start(m_widget, args);
921 * \brief Loads a file or url and starts playback
923 * \param url File patho or url
925 void QMPwidget::load(const QString &url)
927 Q_ASSERT_X(m_process->state() != QProcess::NotRunning, "QMPwidget::load()", "MPlayer process not started yet");
929 // From the MPlayer slave interface documentation:
930 // "Try using something like [the following] to switch to the next file.
931 // It avoids audio playback starting to play the old file for a short time
932 // before switching to the new one.
933 writeCommand("pausing_keep_force pt_step 1");
934 writeCommand("get_property pause");
936 if (url.endsWith(".txt", Qt::CaseInsensitive))
937 writeCommand(QString("loadlist '%1'").arg(url));
938 else
939 writeCommand(QString("loadfile '%1'").arg(url));
943 * \brief Resumes playback
945 void QMPwidget::play()
947 if (m_process->m_state == PausedState) {
948 m_process->pause();
953 * \brief Pauses playback
955 void QMPwidget::pause()
957 if (m_process->m_state == PlayingState) {
958 m_process->pause();
963 * \brief Stops playback
965 void QMPwidget::stop()
967 m_process->stop();
971 * \brief Media playback seeking
973 * \param offset Seeking offset in seconds
974 * \param whence Seeking mode
975 * \returns \p true If the seeking mode is valid
976 * \sa tell()
978 bool QMPwidget::seek(int offset, int whence)
980 return seek(double(offset), whence);
984 * \brief Media playback seeking
986 * \param offset Seeking offset in seconds
987 * \param whence Seeking mode
988 * \returns \p true If the seeking mode is valid
989 * \sa tell()
991 bool QMPwidget::seek(double offset, int whence)
993 m_seekTimer.stop(); // Cancel all current seek requests
995 switch (whence) {
996 case RelativeSeek:
997 case PercentageSeek:
998 case AbsoluteSeek:
999 break;
1000 default:
1001 return false;
1004 // Schedule seek request
1005 m_seekCommand = QString("seek %1 %2").arg(offset).arg(whence);
1006 m_seekTimer.start();
1007 return true;
1011 * \brief Toggles full-screen mode
1013 void QMPwidget::toggleFullScreen()
1015 if (!isFullScreen()) {
1016 m_windowFlags = windowFlags() & (Qt::Window);
1017 m_geometry = geometry();
1018 setWindowFlags((windowFlags() | Qt::Window));
1019 // From Phonon::VideoWidget
1020 #ifdef Q_WS_X11
1021 show();
1022 raise();
1023 setWindowState(windowState() | Qt::WindowFullScreen);
1024 #else
1025 setWindowState(windowState() | Qt::WindowFullScreen);
1026 show();
1027 #endif
1028 } else {
1029 setWindowFlags((windowFlags() ^ (Qt::Window)) | m_windowFlags);
1030 setWindowState(windowState() & ~Qt::WindowFullScreen);
1031 setGeometry(m_geometry);
1032 show();
1037 * \brief Sends a command to the MPlayer process
1038 * \details
1039 * Since MPlayer is being run in slave mode, it reads commands from the standard
1040 * input. It is assumed that the interface provided by this class might not be
1041 * sufficient for some situations, so you can use this functions to directly
1042 * control the MPlayer process.
1044 * For a complete list of commands for MPlayer's slave mode, see
1045 * http://www.mplayerhq.hu/DOCS/tech/slave.txt .
1047 * \param command The command line. A newline character will be added internally.
1049 void QMPwidget::writeCommand(const QString &command)
1051 m_process->writeCommand(command);
1055 * \brief Mouse double click event handler
1056 * \details
1057 * This implementation will toggle full screen and accept the event
1059 * \param event Mouse event
1061 void QMPwidget::mouseDoubleClickEvent(QMouseEvent *event)
1063 toggleFullScreen();
1064 event->accept();
1068 * \brief Keyboard press event handler
1069 * \details
1070 * This implementation tries to resemble the classic MPlayer interface. For a
1071 * full list of supported key codes, see \ref shortcuts.
1073 * \param event Key event
1075 void QMPwidget::keyPressEvent(QKeyEvent *event)
1077 bool accept = true;
1078 switch (event->key()) {
1079 case Qt::Key_P:
1080 case Qt::Key_Space:
1081 if (state() == PlayingState) {
1082 pause();
1083 } else if (state() == PausedState) {
1084 play();
1086 break;
1088 case Qt::Key_F:
1089 toggleFullScreen();
1090 break;
1092 case Qt::Key_Q:
1093 case Qt::Key_Escape:
1094 stop();
1095 break;
1097 case Qt::Key_Minus:
1098 writeCommand("audio_delay -0.1");
1099 break;
1100 case Qt::Key_Plus:
1101 writeCommand("audio_delay 0.1");
1102 break;
1104 case Qt::Key_Left:
1105 seek(-10, RelativeSeek);
1106 break;
1107 case Qt::Key_Right:
1108 seek(10, RelativeSeek);
1109 break;
1110 case Qt::Key_Down:
1111 seek(-60, RelativeSeek);
1112 break;
1113 case Qt::Key_Up:
1114 seek(60, RelativeSeek);
1115 break;
1116 case Qt::Key_PageDown:
1117 seek(-600, RelativeSeek);
1118 break;
1119 case Qt::Key_PageUp:
1120 seek(600, RelativeSeek);
1121 break;
1123 case Qt::Key_Asterisk:
1124 writeCommand("volume 10");
1125 break;
1126 case Qt::Key_Slash:
1127 writeCommand("volume -10");
1128 break;
1130 case Qt::Key_X:
1131 writeCommand("sub_delay 0.1");
1132 break;
1133 case Qt::Key_Z:
1134 writeCommand("sub_delay -0.1");
1135 break;
1137 default:
1138 accept = false;
1139 break;
1142 event->setAccepted(accept);
1146 * \brief Resize event handler
1147 * \details
1148 * If you reimplement this function, you need to call this handler, too.
1150 * \param event Resize event
1152 void QMPwidget::resizeEvent(QResizeEvent *event)
1154 Q_UNUSED(event);
1155 updateWidgetSize();
1158 void QMPwidget::updateWidgetSize()
1160 if (!m_process->m_mediaInfo.size.isNull()) {
1161 QSize mediaSize = m_process->m_mediaInfo.size;
1162 QSize widgetSize = size();
1164 double factor = qMin(double(widgetSize.width()) / mediaSize.width(), double(widgetSize.height()) / mediaSize.height());
1165 QRect wrect(0, 0, int(factor * mediaSize.width() + 0.5), int(factor * mediaSize.height()));
1166 wrect.moveTopLeft(rect().center() - wrect.center());
1167 m_widget->setGeometry(wrect);
1168 } else {
1169 m_widget->setGeometry(QRect(QPoint(0, 0), size()));
1174 void QMPwidget::delayedSeek()
1176 if (!m_seekCommand.isEmpty()) {
1177 writeCommand(m_seekCommand);
1178 m_seekCommand = QString();
1182 void QMPwidget::setVolume(int volume)
1184 writeCommand(QString("volume %1 1").arg(volume));
1187 void QMPwidget::mpStateChanged(int state)
1189 if (m_seekSlider != NULL && state == PlayingState && m_process->m_mediaInfo.ok) {
1190 m_seekSlider->setRange(0, m_process->m_mediaInfo.length);
1191 m_seekSlider->setEnabled(m_process->m_mediaInfo.seekable);
1194 updateWidgetSize();
1195 emit stateChanged(state);
1198 void QMPwidget::mpStreamPositionChanged(double position)
1200 if (m_seekSlider != NULL && m_seekCommand.isEmpty() && m_seekSlider->value() != qRound(position)) {
1201 m_seekSlider->disconnect(this);
1202 m_seekSlider->setValue(qRound(position));
1203 connect(m_seekSlider, SIGNAL(valueChanged(int)), this, SLOT(seek(int)));
1207 void QMPwidget::mpVolumeChanged(int volume)
1209 if (m_volumeSlider != NULL) {
1210 m_volumeSlider->disconnect(this);
1211 m_volumeSlider->setValue(volume);
1212 connect(m_seekSlider, SIGNAL(valueChanged(int)), this, SLOT(setVolume(int)));
1217 #include "qmpwidget.moc"
1220 /* Documentation follows */
1223 * \class QMPwidget
1224 * \brief A Qt widget for embedding MPlayer
1225 * \details
1227 * \section Overview
1229 * \subsection comm MPlayer communication
1231 * If you want to communicate with MPlayer through its
1232 * <a href="http://www.mplayerhq.hu/DOCS/tech/slave.txt">slave mode protocol</a>,
1233 * you can use the writeCommand() slot. If MPlayer writes to its standard output
1234 * or standard error channel, the signals readStandardOutput() and
1235 * readStandardError() will be emitted.
1237 * \subsection controls Graphical controls
1239 * You can connect sliders for seeking and volume adjustment to an instance of
1240 * this class. Please use setSeekSlider() and setVolumeSlider(), respectively.
1242 * \section example Usage example
1244 * A minimal example using this widget to play a low-res version of
1245 * <a href="http://www.bigbuckbunny.org/">Big Buck Bunny</a> might look as follows.
1246 * Please note that the actual movie URL has been shortened for the sake of clarity.
1247 \code
1248 #include <QApplication>
1249 #include "qmpwidget.h"
1251 // Program entry point
1252 int main(int argc, char **argv)
1254 QApplication app(argc, argv);
1256 QMPwidget widget;
1257 widget.show();
1258 widget.start(QStringList("http://tinyurl.com/2vs2kg5"));
1260 return app.exec();
1262 \endcode
1265 * For further information about this project, please refer to the
1266 * <a href="index.html">main page</a>.
1270 * \enum QMPwidget::State
1271 * \brief MPlayer state
1272 * \details
1273 * This enumeration is somewhat identical to <a href="http://doc.trolltech.com/phonon.html#State-enum">
1274 * Phonon's State enumeration</a>, except that it has an additional
1275 * member which is used when the MPlayer process has not been started yet (NotStartedState)
1277 * <table>
1278 * <tr><th>Constant</th><th>Value</th><th>Description</th></tr>
1279 * <tr>
1280 * <td>\p QMPwidget::NotStartedState</td>
1281 * <td>\p -1</td>
1282 * <td>The Mplayer process has not been started yet or has already terminated.</td>
1283 * </tr>
1284 * <tr>
1285 * <td>\p QMPwidget::IdleState</td>
1286 * <td>\p 0</td>
1287 * <td>The MPlayer process has been started, but is idle and waiting for commands.</td>
1288 * </tr>
1289 * <tr>
1290 * <td>\p QMPwidget::LoadingState</td>
1291 * <td>\p 1</td>
1292 * <td>The media file is being loaded, but playback has not been started yet.</td>
1293 * </tr>
1294 * <tr>
1295 * <td>\p QMPwidget::StoppedState</td>
1296 * <td>\p 2</td>
1297 * <td>This constant is deprecated and is not being used</td>
1298 * </tr>
1299 * <tr>
1300 * <td>\p QMPwidget::PlayingState</td>
1301 * <td>\p 3</td>
1302 * <td></td>
1303 * </tr>
1304 * <tr>
1305 * <td>\p QMPwidget::BufferingState</td>
1306 * <td>\p 4</td>
1307 * <td></td>
1308 * </tr>
1309 * <tr>
1310 * <td>\p QMPwidget::PausedState</td>
1311 * <td>\p 5</td>
1312 * <td></td>
1313 * </tr>
1314 * <tr>
1315 * <td>\p QMPwidget::ErrorState</td>
1316 * <td>\p 6</td>
1317 * <td></td>
1318 * </tr>
1319 * </table>
1323 * \enum QMPwidget::Mode
1324 * \brief Video playback modes
1325 * \details
1326 * This enumeration describes valid modes for video playback. Please see \ref playbackmodes for a
1327 * detailed description of both modes.
1329 * <table>
1330 * <tr><th>Constant</th><th>Value</th><th>Description</th></tr>
1331 * <tr>
1332 * <td>\p QMPwidget::EmbeddedMode</td>
1333 * <td>\p 0</td>
1334 * <td>MPlayer will render directly into a Qt widget.</td>
1335 * </tr>
1336 * <tr>
1337 * <td>\p QMPwidget::PipedMode</td>
1338 * <td>\p 1</td>
1339 * <td>MPlayer will write the video data into a FIFO which will be parsed in a seperate thread.\n
1340 The frames will be rendered by QMPwidget.</td>
1341 * </tr>
1342 * </table>
1346 * \enum QMPwidget::SeekMode
1347 * \brief Seeking modes
1348 * \details
1349 * This enumeration describes valid modes for seeking the media stream.
1351 * <table>
1352 * <tr><th>Constant</th><th>Value</th><th>Description</th></tr>
1353 * <tr>
1354 * <td>\p QMPwidget::RelativeSeek</td>
1355 * <td>\p 0</td>
1356 * <td>Relative seek in seconds</td>
1357 * </tr>
1358 * <tr>
1359 * <td>\p QMPwidget::PercantageSeek</td>
1360 * <td>\p 1</td>
1361 * <td>Seek to a position given by a percentage of the whole movie duration</td>
1362 * </tr>
1363 * <tr>
1364 * <td>\p QMPwidget::AbsoluteSeek</td>
1365 * <td>\p 2</td>
1366 * <td>Seek to a position given by an absolute time</td>
1367 * </tr>
1368 * </table>
1372 * \fn void QMPwidget::stateChanged(int state)
1373 * \brief Emitted if the state has changed
1374 * \details
1375 * This signal is emitted when the state of the MPlayer process changes.
1377 * \param state The new state
1381 * \fn void QMPwidget::error(const QString &reason)
1382 * \brief Emitted if the state has changed to QMPwidget::ErrorState
1383 * \details
1384 * This signal is emitted when the state of the MPlayer process changes to QMPwidget::ErrorState.
1386 * \param reason Textual error description (may be empty)
1390 * \fn void QMPwidget::readStandardOutput(const QString &line)
1391 * \brief Signal for reading MPlayer's standard output
1392 * \details
1393 * This signal is emitted when MPlayer wrote a line of text to its standard output channel.
1397 * \fn void QMPwidget::readStandardError(const QString &line)
1398 * \brief Signal for reading MPlayer's standard error
1399 * \details
1400 * This signal is emitted when MPlayer wrote a line of text to its standard error channel.