Implement stream position slider.
[wallplayer.git] / src / qmpwidget.cpp
blobc29aefdb36608627db186bd0042fbf3d23f345a0
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 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 (qAbs(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;
1090 case Qt::Key_F:
1091 toggleFullScreen();
1092 break;
1094 case Qt::Key_Q:
1095 case Qt::Key_Escape:
1096 stop();
1097 break;
1099 case Qt::Key_Minus:
1100 writeCommand("audio_delay -0.1");
1101 break;
1102 case Qt::Key_Plus:
1103 writeCommand("audio_delay 0.1");
1104 break;
1106 case Qt::Key_Left:
1107 seek(-10, RelativeSeek);
1108 break;
1109 case Qt::Key_Right:
1110 seek(10, RelativeSeek);
1111 break;
1112 case Qt::Key_Down:
1113 seek(-60, RelativeSeek);
1114 break;
1115 case Qt::Key_Up:
1116 seek(60, RelativeSeek);
1117 break;
1118 case Qt::Key_PageDown:
1119 seek(-600, RelativeSeek);
1120 break;
1121 case Qt::Key_PageUp:
1122 seek(600, RelativeSeek);
1123 break;
1125 case Qt::Key_Asterisk:
1126 writeCommand("volume 10");
1127 break;
1128 case Qt::Key_Slash:
1129 writeCommand("volume -10");
1130 break;
1132 case Qt::Key_X:
1133 writeCommand("sub_delay 0.1");
1134 break;
1135 case Qt::Key_Z:
1136 writeCommand("sub_delay -0.1");
1137 break;
1139 default:
1140 accept = false;
1141 break;
1144 event->setAccepted(accept);
1148 * \brief Resize event handler
1149 * \details
1150 * If you reimplement this function, you need to call this handler, too.
1152 * \param event Resize event
1154 void QMPwidget::resizeEvent(QResizeEvent *event)
1156 Q_UNUSED(event);
1157 updateWidgetSize();
1160 void QMPwidget::updateWidgetSize()
1162 if (!m_process->m_mediaInfo.size.isNull()) {
1163 QSize mediaSize = m_process->m_mediaInfo.size;
1164 QSize widgetSize = size();
1166 double factor = qMin(double(widgetSize.width()) / mediaSize.width(), double(widgetSize.height()) / mediaSize.height());
1167 QRect wrect(0, 0, int(factor * mediaSize.width() + 0.5), int(factor * mediaSize.height()));
1168 wrect.moveTopLeft(rect().center() - wrect.center());
1169 m_widget->setGeometry(wrect);
1170 } else {
1171 m_widget->setGeometry(QRect(QPoint(0, 0), size()));
1176 void QMPwidget::delayedSeek()
1178 if (!m_seekCommand.isEmpty()) {
1179 writeCommand(m_seekCommand);
1180 m_seekCommand = QString();
1184 void QMPwidget::setVolume(int volume)
1186 writeCommand(QString("volume %1 1").arg(volume));
1189 void QMPwidget::mpStateChanged(int state)
1191 if (m_seekSlider != NULL && state == PlayingState && m_process->m_mediaInfo.ok) {
1192 m_seekSlider->setRange(0, m_process->m_mediaInfo.length);
1193 m_seekSlider->setEnabled(m_process->m_mediaInfo.seekable);
1196 updateWidgetSize();
1197 emit stateChanged(state);
1200 void QMPwidget::mpStreamPositionChanged(double position)
1202 if (m_seekSlider != NULL && m_seekCommand.isEmpty() && m_seekSlider->value() != qRound(position)) {
1203 m_seekSlider->disconnect(this);
1204 m_seekSlider->setValue(qRound(position));
1205 connect(m_seekSlider, SIGNAL(valueChanged(int)), this, SLOT(seek(int)));
1209 void QMPwidget::mpVolumeChanged(int volume)
1211 if (m_volumeSlider != NULL) {
1212 m_volumeSlider->disconnect(this);
1213 m_volumeSlider->setValue(volume);
1214 connect(m_seekSlider, SIGNAL(valueChanged(int)), this, SLOT(setVolume(int)));
1219 #include "qmpwidget.moc"
1222 /* Documentation follows */
1225 * \class QMPwidget
1226 * \brief A Qt widget for embedding MPlayer
1227 * \details
1229 * \section Overview
1231 * \subsection comm MPlayer communication
1233 * If you want to communicate with MPlayer through its
1234 * <a href="http://www.mplayerhq.hu/DOCS/tech/slave.txt">slave mode protocol</a>,
1235 * you can use the writeCommand() slot. If MPlayer writes to its standard output
1236 * or standard error channel, the signals readStandardOutput() and
1237 * readStandardError() will be emitted.
1239 * \subsection controls Graphical controls
1241 * You can connect sliders for seeking and volume adjustment to an instance of
1242 * this class. Please use setSeekSlider() and setVolumeSlider(), respectively.
1244 * \section example Usage example
1246 * A minimal example using this widget to play a low-res version of
1247 * <a href="http://www.bigbuckbunny.org/">Big Buck Bunny</a> might look as follows.
1248 * Please note that the actual movie URL has been shortened for the sake of clarity.
1249 \code
1250 #include <QApplication>
1251 #include "qmpwidget.h"
1253 // Program entry point
1254 int main(int argc, char **argv)
1256 QApplication app(argc, argv);
1258 QMPwidget widget;
1259 widget.show();
1260 widget.start(QStringList("http://tinyurl.com/2vs2kg5"));
1262 return app.exec();
1264 \endcode
1267 * For further information about this project, please refer to the
1268 * <a href="index.html">main page</a>.
1272 * \enum QMPwidget::State
1273 * \brief MPlayer state
1274 * \details
1275 * This enumeration is somewhat identical to <a href="http://doc.trolltech.com/phonon.html#State-enum">
1276 * Phonon's State enumeration</a>, except that it has an additional
1277 * member which is used when the MPlayer process has not been started yet (NotStartedState)
1279 * <table>
1280 * <tr><th>Constant</th><th>Value</th><th>Description</th></tr>
1281 * <tr>
1282 * <td>\p QMPwidget::NotStartedState</td>
1283 * <td>\p -1</td>
1284 * <td>The Mplayer process has not been started yet or has already terminated.</td>
1285 * </tr>
1286 * <tr>
1287 * <td>\p QMPwidget::IdleState</td>
1288 * <td>\p 0</td>
1289 * <td>The MPlayer process has been started, but is idle and waiting for commands.</td>
1290 * </tr>
1291 * <tr>
1292 * <td>\p QMPwidget::LoadingState</td>
1293 * <td>\p 1</td>
1294 * <td>The media file is being loaded, but playback has not been started yet.</td>
1295 * </tr>
1296 * <tr>
1297 * <td>\p QMPwidget::StoppedState</td>
1298 * <td>\p 2</td>
1299 * <td>This constant is deprecated and is not being used</td>
1300 * </tr>
1301 * <tr>
1302 * <td>\p QMPwidget::PlayingState</td>
1303 * <td>\p 3</td>
1304 * <td></td>
1305 * </tr>
1306 * <tr>
1307 * <td>\p QMPwidget::BufferingState</td>
1308 * <td>\p 4</td>
1309 * <td></td>
1310 * </tr>
1311 * <tr>
1312 * <td>\p QMPwidget::PausedState</td>
1313 * <td>\p 5</td>
1314 * <td></td>
1315 * </tr>
1316 * <tr>
1317 * <td>\p QMPwidget::ErrorState</td>
1318 * <td>\p 6</td>
1319 * <td></td>
1320 * </tr>
1321 * </table>
1325 * \enum QMPwidget::Mode
1326 * \brief Video playback modes
1327 * \details
1328 * This enumeration describes valid modes for video playback. Please see \ref playbackmodes for a
1329 * detailed description of both modes.
1331 * <table>
1332 * <tr><th>Constant</th><th>Value</th><th>Description</th></tr>
1333 * <tr>
1334 * <td>\p QMPwidget::EmbeddedMode</td>
1335 * <td>\p 0</td>
1336 * <td>MPlayer will render directly into a Qt widget.</td>
1337 * </tr>
1338 * <tr>
1339 * <td>\p QMPwidget::PipedMode</td>
1340 * <td>\p 1</td>
1341 * <td>MPlayer will write the video data into a FIFO which will be parsed in a seperate thread.\n
1342 The frames will be rendered by QMPwidget.</td>
1343 * </tr>
1344 * </table>
1348 * \enum QMPwidget::SeekMode
1349 * \brief Seeking modes
1350 * \details
1351 * This enumeration describes valid modes for seeking the media stream.
1353 * <table>
1354 * <tr><th>Constant</th><th>Value</th><th>Description</th></tr>
1355 * <tr>
1356 * <td>\p QMPwidget::RelativeSeek</td>
1357 * <td>\p 0</td>
1358 * <td>Relative seek in seconds</td>
1359 * </tr>
1360 * <tr>
1361 * <td>\p QMPwidget::PercantageSeek</td>
1362 * <td>\p 1</td>
1363 * <td>Seek to a position given by a percentage of the whole movie duration</td>
1364 * </tr>
1365 * <tr>
1366 * <td>\p QMPwidget::AbsoluteSeek</td>
1367 * <td>\p 2</td>
1368 * <td>Seek to a position given by an absolute time</td>
1369 * </tr>
1370 * </table>
1374 * \fn void QMPwidget::stateChanged(int state)
1375 * \brief Emitted if the state has changed
1376 * \details
1377 * This signal is emitted when the state of the MPlayer process changes.
1379 * \param state The new state
1383 * \fn void QMPwidget::error(const QString &reason)
1384 * \brief Emitted if the state has changed to QMPwidget::ErrorState
1385 * \details
1386 * This signal is emitted when the state of the MPlayer process changes to QMPwidget::ErrorState.
1388 * \param reason Textual error description (may be empty)
1392 * \fn void QMPwidget::readStandardOutput(const QString &line)
1393 * \brief Signal for reading MPlayer's standard output
1394 * \details
1395 * This signal is emitted when MPlayer wrote a line of text to its standard output channel.
1399 * \fn void QMPwidget::readStandardError(const QString &line)
1400 * \brief Signal for reading MPlayer's standard error
1401 * \details
1402 * This signal is emitted when MPlayer wrote a line of text to its standard error channel.