Misc. fixes.
[wallplayer.git] / src / qmpwidget.cpp
bloba4c10c883df99c8b40231bedf676f75ee408a67a
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(500);
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 myargs += "-osdlevel";
276 myargs += "1";
277 if (!useFakeInputconf) {
278 myargs += "-input";
279 myargs += "nodefault-bindings:conf=/dev/null";
280 } else {
281 #ifndef Q_WS_WIN
282 // Ugly hack for older versions of mplayer (used in kmplayer and other)
283 if (m_fakeInputconf == NULL) {
284 m_fakeInputconf = new QTemporaryFile();
285 if (m_fakeInputconf->open()) {
286 writeFakeInputconf(m_fakeInputconf);
287 } else {
288 delete m_fakeInputconf;
289 m_fakeInputconf = NULL;
292 if (m_fakeInputconf != NULL) {
293 myargs += "-input";
294 myargs += QString("conf=%1").arg(m_fakeInputconf->fileName());
296 #endif
299 if (m_mode == QMPwidget::EmbeddedMode) {
300 myargs += "-wid";
301 myargs += QString::number((int)widget->winId());
302 if (!m_videoOutput.isEmpty()) {
303 myargs += "-vo";
304 myargs += m_videoOutput;
306 } else {
307 #ifdef QMP_USE_YUVPIPE
308 myargs += "-vo";
309 myargs += QString("yuv4mpeg:file=%1").arg(m_yuvReader->m_pipe);
310 #endif
313 myargs += args;
314 #ifdef QMP_DEBUG_OUTPUT
315 qDebug() << myargs;
316 #endif
317 QProcess::start(m_mplayerPath, myargs);
318 changeState(QMPwidget::IdleState);
320 if (m_mode == QMPwidget::PipeMode) {
321 #ifdef QMP_USE_YUVPIPE
322 connect(m_yuvReader, SIGNAL(imageReady(const QImage &)), widget, SLOT(displayImage(const QImage &)));
323 m_yuvReader->start();
324 #endif
328 QString mplayerVersion()
330 QProcess p;
331 p.start(m_mplayerPath, QStringList("-version"));
332 if (!p.waitForStarted()) {
333 return QString();
335 if (!p.waitForFinished()) {
336 return QString();
339 QString output = QString(p.readAll());
340 QRegExp re("MPlayer ([^ ]*)");
341 if (re.indexIn(output) > -1) {
342 return re.cap(1);
344 return output;
347 QProcess::ProcessState processState() const
349 return QProcess::state();
352 void writeCommand(const QString &command)
354 #ifdef QMP_DEBUG_OUTPUT
355 qDebug("in: \"%s\"", qPrintable(command));
356 #endif
357 QProcess::write(command.toLocal8Bit()+"\n");
360 void quit()
362 writeCommand("quit");
363 QProcess::waitForFinished(100);
364 if (QProcess::state() == QProcess::Running) {
365 QProcess::kill();
367 QProcess::waitForFinished(-1);
370 void pause()
372 writeCommand("pause");
375 void stop()
377 changeState(QMPwidget::StoppedState);
378 writeCommand("stop");
381 signals:
382 void stateChanged(int state);
383 void streamPositionChanged(double position);
384 void error(const QString &reason);
386 void readStandardOutput(const QString &line);
387 void readStandardError(const QString &line);
389 private slots:
390 void readStdout()
392 QStringList lines = QString::fromLocal8Bit(readAllStandardOutput()).split("\n", QString::SkipEmptyParts);
393 for (int i = 0; i < lines.count(); i++) {
394 lines[i].remove("\r");
395 #ifdef QMP_DEBUG_OUTPUT
396 qDebug("out: \"%s\"", qPrintable(lines[i]));
397 #endif
398 parseLine(lines[i]);
399 emit readStandardOutput(lines[i]);
403 void readStderr()
405 QStringList lines = QString::fromLocal8Bit(readAllStandardError()).split("\n", QString::SkipEmptyParts);
406 for (int i = 0; i < lines.count(); i++) {
407 lines[i].remove("\r");
408 #ifdef QMP_DEBUG_OUTPUT
409 qDebug("err: \"%s\"", qPrintable(lines[i]));
410 #endif
411 parseLine(lines[i]);
412 emit readStandardError(lines[i]);
416 void finished()
418 // Called if the *process* has finished
419 changeState(QMPwidget::NotStartedState);
422 void movieFinished()
424 if (m_state == QMPwidget::PlayingState) {
425 changeState(QMPwidget::IdleState);
429 private:
430 // Parses a line of MPlayer output
431 void parseLine(const QString &line)
433 if (line.startsWith("Playing ")) {
434 changeState(QMPwidget::LoadingState);
435 } else if (line.startsWith("Cache fill:")) {
436 changeState(QMPwidget::BufferingState);
437 } else if (line.startsWith("Starting playback...")) {
438 m_mediaInfo.ok = true; // No more info here
439 changeState(QMPwidget::PlayingState);
440 } else if (line.startsWith("File not found: ")) {
441 changeState(QMPwidget::ErrorState);
442 } else if (line.endsWith("ID_PAUSED")) {
443 changeState(QMPwidget::PausedState);
444 } else if (line.startsWith("ID_")) {
445 parseMediaInfo(line);
446 } else if (line.startsWith("No stream found")) {
447 changeState(QMPwidget::ErrorState, line);
448 } else if (line.startsWith("A:") || line.startsWith("V:")) {
449 if (m_state != QMPwidget::PlayingState
450 && m_state != QMPwidget::StoppedState) {
451 changeState(QMPwidget::PlayingState);
453 parsePosition(line);
454 } else if (line.startsWith("Exiting...")) {
455 changeState(QMPwidget::NotStartedState);
459 // Parses MPlayer's media identification output
460 void parseMediaInfo(const QString &line)
462 QStringList info = line.split("=");
463 if (info.count() < 2) {
464 return;
467 if (info[0] == "ID_VIDEO_FORMAT") {
468 m_mediaInfo.videoFormat = info[1];
469 } else if (info[0] == "ID_VIDEO_BITRATE") {
470 m_mediaInfo.videoBitrate = info[1].toInt();
471 } else if (info[0] == "ID_VIDEO_WIDTH") {
472 m_mediaInfo.size.setWidth(info[1].toInt());
473 } else if (info[0] == "ID_VIDEO_HEIGHT") {
474 m_mediaInfo.size.setHeight(info[1].toInt());
475 } else if (info[0] == "ID_VIDEO_FPS") {
476 m_mediaInfo.framesPerSecond = info[1].toDouble();
478 } else if (info[0] == "ID_AUDIO_FORMAT") {
479 m_mediaInfo.audioFormat = info[1];
480 } else if (info[0] == "ID_AUDIO_BITRATE") {
481 m_mediaInfo.audioBitrate = info[1].toInt();
482 } else if (info[0] == "ID_AUDIO_RATE") {
483 m_mediaInfo.sampleRate = info[1].toInt();
484 } else if (info[0] == "ID_AUDIO_NCH") {
485 m_mediaInfo.numChannels = info[1].toInt();
487 } else if (info[0] == "ID_LENGTH") {
488 m_mediaInfo.length = info[1].toDouble();
489 } else if (info[0] == "ID_SEEKABLE") {
490 m_mediaInfo.seekable = (bool)info[1].toInt();
492 } else if (info[0].startsWith("ID_CLIP_INFO_NAME")) {
493 m_currentTag = info[1];
494 } else if (info[0].startsWith("ID_CLIP_INFO_VALUE") && !m_currentTag.isEmpty()) {
495 m_mediaInfo.tags.insert(m_currentTag, info[1]);
499 // Parsas MPlayer's position output
500 void parsePosition(const QString &line)
502 static QRegExp rx("[ :]");
503 QStringList info = line.split(rx, QString::SkipEmptyParts);
505 double oldpos = m_streamPosition;
506 for (int i = 0; i < info.count(); i++) {
507 if (info[i] == "V" && info.count() > i) {
508 m_streamPosition = info[i+1].toDouble();
510 // If the movie is near its end, start a timer that will check whether
511 // the movie has really finished.
512 if ((m_streamPosition - m_mediaInfo.length) < 1) {
513 m_movieFinishedTimer.start();
518 if (oldpos != m_streamPosition) {
519 emit streamPositionChanged(m_streamPosition);
523 // Changes the current state, possibly emitting multiple signals
524 void changeState(QMPwidget::State state, const QString &comment = QString())
526 #ifdef QMP_USE_YUVPIPE
527 if (m_yuvReader != NULL && (state == QMPwidget::ErrorState || state == QMPwidget::NotStartedState)) {
528 m_yuvReader->stop();
529 m_yuvReader->deleteLater();
531 #endif
533 if (m_state == state) {
534 return;
537 if (m_state == QMPwidget::PlayingState) {
538 m_movieFinishedTimer.stop();
541 m_state = state;
542 emit stateChanged(m_state);
544 switch (m_state) {
545 case QMPwidget::NotStartedState:
546 resetValues();
547 break;
549 case QMPwidget::ErrorState:
550 emit error(comment);
551 resetValues();
552 break;
554 default: break;
558 // Resets the media info and position values
559 void resetValues()
561 m_mediaInfo = QMPwidget::MediaInfo();
562 m_streamPosition = -1;
565 // Writes a dummy input configuration to the given device
566 void writeFakeInputconf(QIODevice *device)
568 // Query list of supported keys
569 QProcess p;
570 p.start(m_mplayerPath, QStringList("-input") += "keylist");
571 if (!p.waitForStarted()) {
572 return;
574 if (!p.waitForFinished()) {
575 return;
577 QStringList keys = QString(p.readAll()).split("\n", QString::SkipEmptyParts);
579 // Write dummy command for each key
580 QTextStream out(device);
581 for (int i = 0; i < keys.count(); i++) {
582 keys[i].remove("\r");
583 out << keys[i] << " " << "ignored" << endl;
587 public:
588 QMPwidget::State m_state;
590 QString m_mplayerPath;
591 QString m_videoOutput;
592 QString m_pipe;
593 QMPwidget::Mode m_mode;
595 QMPwidget::MediaInfo m_mediaInfo;
596 double m_streamPosition; // This is the video position
597 QTimer m_movieFinishedTimer;
599 QString m_currentTag;
601 QTemporaryFile *m_fakeInputconf;
603 #ifdef QMP_USE_YUVPIPE
604 QPointer<QMPYuvReader> m_yuvReader;
605 #endif
609 // Initialize the media info structure
610 QMPwidget::MediaInfo::MediaInfo()
611 : videoBitrate(0), framesPerSecond(0), sampleRate(0), numChannels(0),
612 ok(false), length(0), seekable(false)
619 * \brief Constructor
621 * \param parent Parent widget
623 QMPwidget::QMPwidget(QWidget *parent)
624 : QWidget(parent)
626 setFocusPolicy(Qt::StrongFocus);
627 setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
629 #ifdef QT_OPENGL_LIB
630 m_widget = new QMPOpenGLVideoWidget(this);
631 #else
632 m_widget = new QMPPlainVideoWidget(this);
633 #endif
635 QPalette p = palette();
636 p.setColor(QPalette::Window, Qt::black);
637 setPalette(p);
639 m_seekTimer.setInterval(50);
640 m_seekTimer.setSingleShot(true);
641 connect(&m_seekTimer, SIGNAL(timeout()), this, SLOT(delayedSeek()));
643 m_process = new QMPProcess(this);
644 connect(m_process, SIGNAL(stateChanged(int)), this, SLOT(mpStateChanged(int)));
645 connect(m_process, SIGNAL(streamPositionChanged(double)), this, SLOT(mpStreamPositionChanged(double)));
646 connect(m_process, SIGNAL(error(const QString &)), this, SIGNAL(error(const QString &)));
647 connect(m_process, SIGNAL(readStandardOutput(const QString &)), this, SIGNAL(readStandardOutput(const QString &)));
648 connect(m_process, SIGNAL(readStandardError(const QString &)), this, SIGNAL(readStandardError(const QString &)));
652 * \brief Destructor
653 * \details
654 * This function will ask the MPlayer process to quit and block until it has really
655 * finished.
657 QMPwidget::~QMPwidget()
659 if (m_process->processState() == QProcess::Running) {
660 m_process->quit();
662 delete m_process;
666 * \brief Returns the current MPlayer process state
668 * \returns The process state
670 QMPwidget::State QMPwidget::state() const
672 return m_process->m_state;
676 * \brief Returns the current media info object
677 * \details
678 * Please check QMPwidget::MediaInfo::ok to make sure the media
679 * information has been fully parsed.
681 * \returns The media info object
683 QMPwidget::MediaInfo QMPwidget::mediaInfo() const
685 return m_process->m_mediaInfo;
689 * \brief Returns the current playback position
691 * \returns The current playback position in seconds
692 * \sa seek()
694 double QMPwidget::tell() const
696 return m_process->m_streamPosition;
700 * \brief Returns the MPlayer process
702 * \returns The MPlayer process
704 QProcess *QMPwidget::process() const
706 return m_process;
710 * \brief Sets the video playback mode
711 * \details
712 * Please see \ref playbackmodes for a discussion of the available modes.
714 * \param mode The video playback mode
715 * \sa mode()
717 void QMPwidget::setMode(Mode mode)
719 #ifdef QMP_USE_YUVPIPE
720 m_process->m_mode = mode;
721 #else
722 Q_UNUSED(mode)
723 #endif
727 * \brief Returns the current video playback mode
729 * \returns The current video playback mode
730 * \sa setMode()
732 QMPwidget::Mode QMPwidget::mode() const
734 return m_process->m_mode;
738 * \brief Sets the video output mode
739 * \details
740 * The video output mode string will be passed to MPlayer using its \p -vo option.
741 * Please see http://www.mplayerhq.hu/DOCS/HTML/en/video.html for an overview of
742 * available video output modes.
744 * Per default, this string will have the following values:
745 * <table>
746 * <tr><th>System</th><th>Configuration</th><th>Value</th></tr>
747 * <tr>
748 * <td>Windows</td>
749 * <td></td>
750 * <td>\p "directx,directx:noaccel"</td>
751 * </tr>
752 * <tr>
753 * <td>X11</td>
754 * <td>Compiled without OpenGL support</td>
755 * <td>\p "xv"</td>
756 * </tr>
757 * <tr>
758 * <td>X11</td>
759 * <td>Compiled with OpenGL support</td>
760 * <td>\p "gl2,gl,xv"</td>
761 * </tr>
762 * <tr>
763 * <td>Mac OS X</td>
764 * <td>Compiled without OpenGL support</td>
765 * <td>\p "quartz"</td>
766 * </tr>
767 * <tr>
768 * <td>Mac OS X</td>
769 * <td>Compiled with OpenGL support</td>
770 * <td>\p "gl,quartz"</td>
771 * </tr>
772 * </table>
775 * \param output The video output mode string
776 * \sa videoOutput()
778 void QMPwidget::setVideoOutput(const QString &output)
780 m_process->m_videoOutput = output;
784 * \brief Returns the current video output mode
786 * \returns The current video output mode
787 * \sa setVideoOutput()
789 QString QMPwidget::videoOutput() const
791 return m_process->m_videoOutput;
795 * \brief Sets the path to the MPlayer executable
796 * \details
797 * Per default, it is assumed the MPlayer executable is
798 * available in the current OS path. Therefore, this value is
799 * set to "mplayer".
801 * \param path Path to the MPlayer executable
802 * \sa mplayerPath()
804 void QMPwidget::setMPlayerPath(const QString &path)
806 m_process->m_mplayerPath = path;
810 * \brief Returns the current path to the MPlayer executable
812 * \returns The path to the MPlayer executable
813 * \sa setMPlayerPath()
815 QString QMPwidget::mplayerPath() const
817 return m_process->m_mplayerPath;
821 * \brief Returns the version string of the MPlayer executable
822 * \details
823 * If the mplayer
826 * \returns The version string of the MPlayer executable
828 QString QMPwidget::mplayerVersion()
830 return m_process->mplayerVersion();
834 * \brief Sets a seeking slider for this widget
836 void QMPwidget::setSeekSlider(QAbstractSlider *slider)
838 if (m_seekSlider) {
839 m_seekSlider->disconnect(this);
840 disconnect(m_seekSlider);
843 if (m_process->m_mediaInfo.ok) {
844 slider->setRange(0, m_process->m_mediaInfo.length);
846 if (m_process->m_mediaInfo.ok) {
847 slider->setEnabled(m_process->m_mediaInfo.seekable);
850 connect(slider, SIGNAL(valueChanged(int)), this, SLOT(seek(int)));
851 m_seekSlider = slider;
855 * \brief Sets a volume slider for this widget
857 void QMPwidget::setVolumeSlider(QAbstractSlider *slider)
859 if (m_volumeSlider) {
860 m_volumeSlider->disconnect(this);
861 disconnect(m_volumeSlider);
865 slider->setRange(0, 100);
866 slider->setValue(100); // TODO
868 connect(slider, SIGNAL(valueChanged(int)), this, SLOT(setVolume(int)));
869 m_volumeSlider = slider;
873 * \brief Shows a custom image
874 * \details
875 * This function sets a custom image that will be shown instead of the MPlayer
876 * video output. In order to show MPlayer's output again, call this function
877 * with a null image.
879 * \note If the current playback mode is not set to \p PipeMode, this function
880 * will have no effect if MPlayer draws to the widget.
882 * \param image Custom image
884 void QMPwidget::showImage(const QImage &image)
886 #ifdef QT_OPENGL_LIB
887 qobject_cast<QMPOpenGLVideoWidget *>(m_widget)->showUserImage(image);
888 #else
889 qobject_cast<QMPPlainVideoWidget*>(m_widget)->showUserImage(image);
890 #endif
894 * \brief Returns a suitable size hint for this widget
895 * \details
896 * This function is used internally by Qt.
898 QSize QMPwidget::sizeHint() const
900 if (m_process->m_mediaInfo.ok && !m_process->m_mediaInfo.size.isNull()) {
901 return m_process->m_mediaInfo.size;
903 return QWidget::sizeHint();
907 * \brief Starts the MPlayer process with the given arguments
908 * \details
909 * If there's another process running, it will be terminated first. MPlayer
910 * will be run in idle mode and is avaiting your commands, e.g. via load().
912 * \param args MPlayer command line arguments
914 void QMPwidget::start(const QStringList &args)
916 if (m_process->processState() == QProcess::Running) {
917 m_process->quit();
919 m_process->start(m_widget, args);
923 * \brief Loads a file or url and starts playback
925 * \param url File patho or url
927 void QMPwidget::load(const QString &url)
929 Q_ASSERT_X(m_process->state() != QProcess::NotRunning, "QMPwidget::load()", "MPlayer process not started yet");
931 // From the MPlayer slave interface documentation:
932 // "Try using something like [the following] to switch to the next file.
933 // It avoids audio playback starting to play the old file for a short time
934 // before switching to the new one.
935 writeCommand("pausing_keep_force pt_step 1");
936 writeCommand("get_property pause");
938 if (url.endsWith(".txt", Qt::CaseInsensitive))
939 writeCommand(QString("loadlist '%1'").arg(url));
940 else
941 writeCommand(QString("loadfile '%1'").arg(url));
945 * \brief Resumes playback
947 void QMPwidget::play()
949 if (m_process->m_state == PausedState) {
950 m_process->pause();
955 * \brief Pauses playback
957 void QMPwidget::pause()
959 if (m_process->m_state == PlayingState) {
960 m_process->pause();
965 * \brief Stops playback
967 void QMPwidget::stop()
969 m_process->stop();
973 * \brief Media playback seeking
975 * \param offset Seeking offset in seconds
976 * \param whence Seeking mode
977 * \returns \p true If the seeking mode is valid
978 * \sa tell()
980 bool QMPwidget::seek(int offset, int whence)
982 return seek(double(offset), whence);
986 * \brief Media playback seeking
988 * \param offset Seeking offset in seconds
989 * \param whence Seeking mode
990 * \returns \p true If the seeking mode is valid
991 * \sa tell()
993 bool QMPwidget::seek(double offset, int whence)
995 m_seekTimer.stop(); // Cancel all current seek requests
997 switch (whence) {
998 case RelativeSeek:
999 case PercentageSeek:
1000 case AbsoluteSeek:
1001 break;
1002 default:
1003 return false;
1006 // Schedule seek request
1007 m_seekCommand = QString("seek %1 %2").arg(offset).arg(whence);
1008 m_seekTimer.start();
1009 return true;
1013 * \brief Toggles full-screen mode
1015 void QMPwidget::toggleFullScreen()
1017 if (!isFullScreen()) {
1018 m_windowFlags = windowFlags() & (Qt::Window);
1019 m_geometry = geometry();
1020 setWindowFlags((windowFlags() | Qt::Window));
1021 // From Phonon::VideoWidget
1022 #ifdef Q_WS_X11
1023 show();
1024 raise();
1025 setWindowState(windowState() | Qt::WindowFullScreen);
1026 #else
1027 setWindowState(windowState() | Qt::WindowFullScreen);
1028 show();
1029 #endif
1030 } else {
1031 setWindowFlags((windowFlags() ^ (Qt::Window)) | m_windowFlags);
1032 setWindowState(windowState() & ~Qt::WindowFullScreen);
1033 setGeometry(m_geometry);
1034 show();
1039 * \brief Sends a command to the MPlayer process
1040 * \details
1041 * Since MPlayer is being run in slave mode, it reads commands from the standard
1042 * input. It is assumed that the interface provided by this class might not be
1043 * sufficient for some situations, so you can use this functions to directly
1044 * control the MPlayer process.
1046 * For a complete list of commands for MPlayer's slave mode, see
1047 * http://www.mplayerhq.hu/DOCS/tech/slave.txt .
1049 * \param command The command line. A newline character will be added internally.
1051 void QMPwidget::writeCommand(const QString &command)
1053 m_process->writeCommand(command);
1057 * \brief Mouse double click event handler
1058 * \details
1059 * This implementation will toggle full screen and accept the event
1061 * \param event Mouse event
1063 void QMPwidget::mouseDoubleClickEvent(QMouseEvent *event)
1065 toggleFullScreen();
1066 event->accept();
1070 * \brief Keyboard press event handler
1071 * \details
1072 * This implementation tries to resemble the classic MPlayer interface. For a
1073 * full list of supported key codes, see \ref shortcuts.
1075 * \param event Key event
1077 void QMPwidget::keyPressEvent(QKeyEvent *event)
1079 bool accept = true;
1080 switch (event->key()) {
1081 case Qt::Key_P:
1082 case Qt::Key_Space:
1083 if (state() == PlayingState) {
1084 pause();
1085 } else if (state() == PausedState) {
1086 play();
1088 break;
1091 case Qt::Key_F:
1092 toggleFullScreen();
1093 break;
1097 case Qt::Key_Q:
1098 case Qt::Key_Escape:
1099 stop();
1100 break;
1103 case Qt::Key_Return:
1104 seek(100000, AbsoluteSeek);
1105 break;
1107 case Qt::Key_Minus:
1108 writeCommand("audio_delay -0.1");
1109 break;
1110 case Qt::Key_Plus:
1111 writeCommand("audio_delay 0.1");
1112 break;
1114 case Qt::Key_Left:
1115 seek(-10, RelativeSeek);
1116 break;
1117 case Qt::Key_Right:
1118 seek(10, RelativeSeek);
1119 break;
1120 case Qt::Key_Down:
1121 seek(-60, RelativeSeek);
1122 break;
1123 case Qt::Key_Up:
1124 seek(60, RelativeSeek);
1125 break;
1126 case Qt::Key_PageDown:
1127 seek(-600, RelativeSeek);
1128 break;
1129 case Qt::Key_PageUp:
1130 seek(600, RelativeSeek);
1131 break;
1133 case Qt::Key_Asterisk:
1134 writeCommand("volume 10");
1135 break;
1136 case Qt::Key_Slash:
1137 writeCommand("volume -10");
1138 break;
1140 case Qt::Key_X:
1141 writeCommand("sub_delay 0.1");
1142 break;
1143 case Qt::Key_Z:
1144 writeCommand("sub_delay -0.1");
1145 break;
1147 default:
1148 accept = false;
1149 break;
1152 event->setAccepted(accept);
1156 * \brief Resize event handler
1157 * \details
1158 * If you reimplement this function, you need to call this handler, too.
1160 * \param event Resize event
1162 void QMPwidget::resizeEvent(QResizeEvent *event)
1164 Q_UNUSED(event);
1165 updateWidgetSize();
1168 void QMPwidget::updateWidgetSize()
1170 if (!m_process->m_mediaInfo.size.isNull()) {
1171 QSize mediaSize = m_process->m_mediaInfo.size;
1172 QSize widgetSize = size();
1174 double factor = qMin(double(widgetSize.width()) / mediaSize.width(), double(widgetSize.height()) / mediaSize.height());
1175 QRect wrect(0, 0, int(factor * mediaSize.width() + 0.5), int(factor * mediaSize.height()));
1176 wrect.moveTopLeft(rect().center() - wrect.center());
1177 m_widget->setGeometry(wrect);
1178 } else {
1179 m_widget->setGeometry(QRect(QPoint(0, 0), size()));
1184 void QMPwidget::delayedSeek()
1186 if (!m_seekCommand.isEmpty()) {
1187 writeCommand(m_seekCommand);
1188 m_seekCommand = QString();
1192 void QMPwidget::setVolume(int volume)
1194 writeCommand(QString("volume %1 1").arg(volume));
1197 void QMPwidget::mpStateChanged(int state)
1199 if (m_seekSlider != NULL && state == PlayingState && m_process->m_mediaInfo.ok) {
1200 m_seekSlider->setRange(0, m_process->m_mediaInfo.length);
1201 m_seekSlider->setEnabled(m_process->m_mediaInfo.seekable);
1204 updateWidgetSize();
1205 emit stateChanged(state);
1208 void QMPwidget::mpStreamPositionChanged(double position)
1210 if (m_seekSlider != NULL && m_seekCommand.isEmpty() && m_seekSlider->value() != qRound(position)) {
1211 m_seekSlider->disconnect(this);
1212 m_seekSlider->setValue(qRound(position));
1213 connect(m_seekSlider, SIGNAL(valueChanged(int)), this, SLOT(seek(int)));
1217 void QMPwidget::mpVolumeChanged(int volume)
1219 if (m_volumeSlider != NULL) {
1220 m_volumeSlider->disconnect(this);
1221 m_volumeSlider->setValue(volume);
1222 connect(m_seekSlider, SIGNAL(valueChanged(int)), this, SLOT(setVolume(int)));
1227 #include "qmpwidget.moc"
1230 /* Documentation follows */
1233 * \class QMPwidget
1234 * \brief A Qt widget for embedding MPlayer
1235 * \details
1237 * \section Overview
1239 * \subsection comm MPlayer communication
1241 * If you want to communicate with MPlayer through its
1242 * <a href="http://www.mplayerhq.hu/DOCS/tech/slave.txt">slave mode protocol</a>,
1243 * you can use the writeCommand() slot. If MPlayer writes to its standard output
1244 * or standard error channel, the signals readStandardOutput() and
1245 * readStandardError() will be emitted.
1247 * \subsection controls Graphical controls
1249 * You can connect sliders for seeking and volume adjustment to an instance of
1250 * this class. Please use setSeekSlider() and setVolumeSlider(), respectively.
1252 * \section example Usage example
1254 * A minimal example using this widget to play a low-res version of
1255 * <a href="http://www.bigbuckbunny.org/">Big Buck Bunny</a> might look as follows.
1256 * Please note that the actual movie URL has been shortened for the sake of clarity.
1257 \code
1258 #include <QApplication>
1259 #include "qmpwidget.h"
1261 // Program entry point
1262 int main(int argc, char **argv)
1264 QApplication app(argc, argv);
1266 QMPwidget widget;
1267 widget.show();
1268 widget.start(QStringList("http://tinyurl.com/2vs2kg5"));
1270 return app.exec();
1272 \endcode
1275 * For further information about this project, please refer to the
1276 * <a href="index.html">main page</a>.
1280 * \enum QMPwidget::State
1281 * \brief MPlayer state
1282 * \details
1283 * This enumeration is somewhat identical to <a href="http://doc.trolltech.com/phonon.html#State-enum">
1284 * Phonon's State enumeration</a>, except that it has an additional
1285 * member which is used when the MPlayer process has not been started yet (NotStartedState)
1287 * <table>
1288 * <tr><th>Constant</th><th>Value</th><th>Description</th></tr>
1289 * <tr>
1290 * <td>\p QMPwidget::NotStartedState</td>
1291 * <td>\p -1</td>
1292 * <td>The Mplayer process has not been started yet or has already terminated.</td>
1293 * </tr>
1294 * <tr>
1295 * <td>\p QMPwidget::IdleState</td>
1296 * <td>\p 0</td>
1297 * <td>The MPlayer process has been started, but is idle and waiting for commands.</td>
1298 * </tr>
1299 * <tr>
1300 * <td>\p QMPwidget::LoadingState</td>
1301 * <td>\p 1</td>
1302 * <td>The media file is being loaded, but playback has not been started yet.</td>
1303 * </tr>
1304 * <tr>
1305 * <td>\p QMPwidget::StoppedState</td>
1306 * <td>\p 2</td>
1307 * <td>This constant is deprecated and is not being used</td>
1308 * </tr>
1309 * <tr>
1310 * <td>\p QMPwidget::PlayingState</td>
1311 * <td>\p 3</td>
1312 * <td></td>
1313 * </tr>
1314 * <tr>
1315 * <td>\p QMPwidget::BufferingState</td>
1316 * <td>\p 4</td>
1317 * <td></td>
1318 * </tr>
1319 * <tr>
1320 * <td>\p QMPwidget::PausedState</td>
1321 * <td>\p 5</td>
1322 * <td></td>
1323 * </tr>
1324 * <tr>
1325 * <td>\p QMPwidget::ErrorState</td>
1326 * <td>\p 6</td>
1327 * <td></td>
1328 * </tr>
1329 * </table>
1333 * \enum QMPwidget::Mode
1334 * \brief Video playback modes
1335 * \details
1336 * This enumeration describes valid modes for video playback. Please see \ref playbackmodes for a
1337 * detailed description of both modes.
1339 * <table>
1340 * <tr><th>Constant</th><th>Value</th><th>Description</th></tr>
1341 * <tr>
1342 * <td>\p QMPwidget::EmbeddedMode</td>
1343 * <td>\p 0</td>
1344 * <td>MPlayer will render directly into a Qt widget.</td>
1345 * </tr>
1346 * <tr>
1347 * <td>\p QMPwidget::PipedMode</td>
1348 * <td>\p 1</td>
1349 * <td>MPlayer will write the video data into a FIFO which will be parsed in a seperate thread.\n
1350 The frames will be rendered by QMPwidget.</td>
1351 * </tr>
1352 * </table>
1356 * \enum QMPwidget::SeekMode
1357 * \brief Seeking modes
1358 * \details
1359 * This enumeration describes valid modes for seeking the media stream.
1361 * <table>
1362 * <tr><th>Constant</th><th>Value</th><th>Description</th></tr>
1363 * <tr>
1364 * <td>\p QMPwidget::RelativeSeek</td>
1365 * <td>\p 0</td>
1366 * <td>Relative seek in seconds</td>
1367 * </tr>
1368 * <tr>
1369 * <td>\p QMPwidget::PercantageSeek</td>
1370 * <td>\p 1</td>
1371 * <td>Seek to a position given by a percentage of the whole movie duration</td>
1372 * </tr>
1373 * <tr>
1374 * <td>\p QMPwidget::AbsoluteSeek</td>
1375 * <td>\p 2</td>
1376 * <td>Seek to a position given by an absolute time</td>
1377 * </tr>
1378 * </table>
1382 * \fn void QMPwidget::stateChanged(int state)
1383 * \brief Emitted if the state has changed
1384 * \details
1385 * This signal is emitted when the state of the MPlayer process changes.
1387 * \param state The new state
1391 * \fn void QMPwidget::error(const QString &reason)
1392 * \brief Emitted if the state has changed to QMPwidget::ErrorState
1393 * \details
1394 * This signal is emitted when the state of the MPlayer process changes to QMPwidget::ErrorState.
1396 * \param reason Textual error description (may be empty)
1400 * \fn void QMPwidget::readStandardOutput(const QString &line)
1401 * \brief Signal for reading MPlayer's standard output
1402 * \details
1403 * This signal is emitted when MPlayer wrote a line of text to its standard output channel.
1407 * \fn void QMPwidget::readStandardError(const QString &line)
1408 * \brief Signal for reading MPlayer's standard error
1409 * \details
1410 * This signal is emitted when MPlayer wrote a line of text to its standard error channel.