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
23 #include <QAbstractSlider>
25 #include <QLocalSocket>
28 #include <QStringList>
29 #include <QTemporaryFile>
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
52 QMPPlainVideoWidget(QWidget
*parent
= 0)
55 setAttribute(Qt::WA_NoSystemBackground
);
56 setMouseTracking(true);
59 void showUserImage(const QImage
&image
)
66 void displayImage(const QImage
&image
)
68 m_pixmap
= QPixmap::fromImage(image
);
73 void paintEvent(QPaintEvent
*event
)
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
);
85 p
.fillRect(rect(), Qt::black
);
98 // A OpenGL video widget
99 class QMPOpenGLVideoWidget
: public QGLWidget
104 QMPOpenGLVideoWidget(QWidget
*parent
= 0)
105 : QGLWidget(parent
), m_tex(-1)
107 setMouseTracking(true);
110 void showUserImage(const QImage
&image
)
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
);
123 glViewport(0, 0, width(), qMax(height(), 1));
129 void displayImage(const QImage
&image
)
131 if (!m_userImage
.isNull()) {
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
);
148 glEnable(GL_TEXTURE_2D
);
149 glClearColor(0, 0, 0, 0);
153 void resizeGL(int w
, int h
)
155 glViewport(0, 0, w
, qMax(h
, 1));
160 glClear(GL_COLOR_BUFFER_BIT
| GL_DEPTH_BUFFER_BIT
);
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());
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);
183 #endif // QT_OPENGL_LIB
186 // A custom QProcess designed for the MPlayer slave interface
187 class QMPProcess
: public QProcess
192 QMPProcess(QObject
*parent
= 0)
193 : QProcess(parent
), m_state(QMPwidget::NotStartedState
), m_mplayerPath("mplayer"),
194 m_fakeInputconf(NULL
)
195 #ifdef QMP_USE_YUVPIPE
202 m_mode
= QMPwidget::EmbeddedMode
;
203 m_videoOutput
= "directx,directx:noaccel";
204 #elif defined(Q_WS_X11)
205 m_mode
= QMPwidget::EmbeddedMode
;
207 m_videoOutput
= "gl2,gl,xv";
209 m_videoOutput
= "xv";
211 #elif defined(Q_WS_MAC)
212 m_mode
= QMPwidget::PipeMode
;
214 m_videoOutput
= "gl,quartz";
216 m_videoOutput
= "quartz";
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()));
231 #ifdef QMP_USE_YUVPIPE
232 if (m_yuvReader
!= NULL
) {
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);
248 m_mode
= QMPwidget::EmbeddedMode
;
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;
269 myargs
+= "-noquiet";
270 myargs
+= "-identify";
271 myargs
+= "-nomouseinput";
272 myargs
+= "-nokeepaspect";
273 myargs
+= "-monitorpixelaspect";
275 myargs
+= "-osdlevel";
277 if (!useFakeInputconf
) {
279 myargs
+= "nodefault-bindings:conf=/dev/null";
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
);
288 delete m_fakeInputconf
;
289 m_fakeInputconf
= NULL
;
292 if (m_fakeInputconf
!= NULL
) {
294 myargs
+= QString("conf=%1").arg(m_fakeInputconf
->fileName());
299 if (m_mode
== QMPwidget::EmbeddedMode
) {
301 myargs
+= QString::number((int)widget
->winId());
302 if (!m_videoOutput
.isEmpty()) {
304 myargs
+= m_videoOutput
;
307 #ifdef QMP_USE_YUVPIPE
309 myargs
+= QString("yuv4mpeg:file=%1").arg(m_yuvReader
->m_pipe
);
314 #ifdef QMP_DEBUG_OUTPUT
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();
328 QString
mplayerVersion()
331 p
.start(m_mplayerPath
, QStringList("-version"));
332 if (!p
.waitForStarted()) {
335 if (!p
.waitForFinished()) {
339 QString output
= QString(p
.readAll());
340 QRegExp
re("MPlayer ([^ ]*)");
341 if (re
.indexIn(output
) > -1) {
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
));
357 QProcess::write(command
.toLocal8Bit()+"\n");
362 writeCommand("quit");
363 QProcess::waitForFinished(100);
364 if (QProcess::state() == QProcess::Running
) {
367 QProcess::waitForFinished(-1);
372 writeCommand("pause");
377 changeState(QMPwidget::StoppedState
);
378 writeCommand("stop");
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
);
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
]));
399 emit
readStandardOutput(lines
[i
]);
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
]));
412 emit
readStandardError(lines
[i
]);
418 // Called if the *process* has finished
419 changeState(QMPwidget::NotStartedState
);
424 if (m_state
== QMPwidget::PlayingState
) {
425 changeState(QMPwidget::IdleState
);
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
);
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) {
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
)) {
529 m_yuvReader
->deleteLater();
533 if (m_state
== state
) {
537 if (m_state
== QMPwidget::PlayingState
) {
538 m_movieFinishedTimer
.stop();
542 emit
stateChanged(m_state
);
545 case QMPwidget::NotStartedState
:
549 case QMPwidget::ErrorState
:
558 // Resets the media info and position values
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
570 p
.start(m_mplayerPath
, QStringList("-input") += "keylist");
571 if (!p
.waitForStarted()) {
574 if (!p
.waitForFinished()) {
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
;
588 QMPwidget::State m_state
;
590 QString m_mplayerPath
;
591 QString m_videoOutput
;
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
;
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)
621 * \param parent Parent widget
623 QMPwidget::QMPwidget(QWidget
*parent
)
626 setFocusPolicy(Qt::StrongFocus
);
627 setSizePolicy(QSizePolicy::Expanding
, QSizePolicy::Expanding
);
630 m_widget
= new QMPOpenGLVideoWidget(this);
632 m_widget
= new QMPPlainVideoWidget(this);
635 QPalette p
= palette();
636 p
.setColor(QPalette::Window
, Qt::black
);
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
&)));
654 * This function will ask the MPlayer process to quit and block until it has really
657 QMPwidget::~QMPwidget()
659 if (m_process
->processState() == QProcess::Running
) {
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
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
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
710 * \brief Sets the video playback mode
712 * Please see \ref playbackmodes for a discussion of the available modes.
714 * \param mode The video playback mode
717 void QMPwidget::setMode(Mode mode
)
719 #ifdef QMP_USE_YUVPIPE
720 m_process
->m_mode
= mode
;
727 * \brief Returns the current video playback mode
729 * \returns The current video playback mode
732 QMPwidget::Mode
QMPwidget::mode() const
734 return m_process
->m_mode
;
738 * \brief Sets the video output mode
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:
746 * <tr><th>System</th><th>Configuration</th><th>Value</th></tr>
750 * <td>\p "directx,directx:noaccel"</td>
754 * <td>Compiled without OpenGL support</td>
759 * <td>Compiled with OpenGL support</td>
760 * <td>\p "gl2,gl,xv"</td>
764 * <td>Compiled without OpenGL support</td>
765 * <td>\p "quartz"</td>
769 * <td>Compiled with OpenGL support</td>
770 * <td>\p "gl,quartz"</td>
775 * \param output The video output mode string
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
797 * Per default, it is assumed the MPlayer executable is
798 * available in the current OS path. Therefore, this value is
801 * \param path Path to the MPlayer executable
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
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
)
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
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
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
)
887 qobject_cast
<QMPOpenGLVideoWidget
*>(m_widget
)->showUserImage(image
);
889 qobject_cast
<QMPPlainVideoWidget
*>(m_widget
)->showUserImage(image
);
894 * \brief Returns a suitable size hint for this widget
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
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
) {
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
));
941 writeCommand(QString("loadfile '%1'").arg(url
));
945 * \brief Resumes playback
947 void QMPwidget::play()
949 if (m_process
->m_state
== PausedState
) {
955 * \brief Pauses playback
957 void QMPwidget::pause()
959 if (m_process
->m_state
== PlayingState
) {
965 * \brief Stops playback
967 void QMPwidget::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
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
993 bool QMPwidget::seek(double offset
, int whence
)
995 m_seekTimer
.stop(); // Cancel all current seek requests
1006 // Schedule seek request
1007 m_seekCommand
= QString("seek %1 %2").arg(offset
).arg(whence
);
1008 m_seekTimer
.start();
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
1025 setWindowState(windowState() | Qt::WindowFullScreen
);
1027 setWindowState(windowState() | Qt::WindowFullScreen
);
1031 setWindowFlags((windowFlags() ^ (Qt::Window
)) | m_windowFlags
);
1032 setWindowState(windowState() & ~Qt::WindowFullScreen
);
1033 setGeometry(m_geometry
);
1039 * \brief Sends a command to the MPlayer process
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
1059 * This implementation will toggle full screen and accept the event
1061 * \param event Mouse event
1063 void QMPwidget::mouseDoubleClickEvent(QMouseEvent
*event
)
1070 * \brief Keyboard press event handler
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
)
1080 switch (event
->key()) {
1083 if (state() == PlayingState
) {
1085 } else if (state() == PausedState
) {
1098 case Qt::Key_Escape:
1103 case Qt::Key_Return
:
1104 seek(100000, AbsoluteSeek
);
1108 writeCommand("audio_delay -0.1");
1111 writeCommand("audio_delay 0.1");
1115 seek(-10, RelativeSeek
);
1118 seek(10, RelativeSeek
);
1121 seek(-60, RelativeSeek
);
1124 seek(60, RelativeSeek
);
1126 case Qt::Key_PageDown
:
1127 seek(-600, RelativeSeek
);
1129 case Qt::Key_PageUp
:
1130 seek(600, RelativeSeek
);
1133 case Qt::Key_Asterisk
:
1134 writeCommand("volume 10");
1137 writeCommand("volume -10");
1141 writeCommand("sub_delay 0.1");
1144 writeCommand("sub_delay -0.1");
1152 event
->setAccepted(accept
);
1156 * \brief Resize event handler
1158 * If you reimplement this function, you need to call this handler, too.
1160 * \param event Resize event
1162 void QMPwidget::resizeEvent(QResizeEvent
*event
)
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
);
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
);
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 */
1234 * \brief A Qt widget for embedding MPlayer
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.
1258 #include <QApplication>
1259 #include "qmpwidget.h"
1261 // Program entry point
1262 int main(int argc, char **argv)
1264 QApplication app(argc, argv);
1268 widget.start(QStringList("http://tinyurl.com/2vs2kg5"));
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
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)
1288 * <tr><th>Constant</th><th>Value</th><th>Description</th></tr>
1290 * <td>\p QMPwidget::NotStartedState</td>
1292 * <td>The Mplayer process has not been started yet or has already terminated.</td>
1295 * <td>\p QMPwidget::IdleState</td>
1297 * <td>The MPlayer process has been started, but is idle and waiting for commands.</td>
1300 * <td>\p QMPwidget::LoadingState</td>
1302 * <td>The media file is being loaded, but playback has not been started yet.</td>
1305 * <td>\p QMPwidget::StoppedState</td>
1307 * <td>This constant is deprecated and is not being used</td>
1310 * <td>\p QMPwidget::PlayingState</td>
1315 * <td>\p QMPwidget::BufferingState</td>
1320 * <td>\p QMPwidget::PausedState</td>
1325 * <td>\p QMPwidget::ErrorState</td>
1333 * \enum QMPwidget::Mode
1334 * \brief Video playback modes
1336 * This enumeration describes valid modes for video playback. Please see \ref playbackmodes for a
1337 * detailed description of both modes.
1340 * <tr><th>Constant</th><th>Value</th><th>Description</th></tr>
1342 * <td>\p QMPwidget::EmbeddedMode</td>
1344 * <td>MPlayer will render directly into a Qt widget.</td>
1347 * <td>\p QMPwidget::PipedMode</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>
1356 * \enum QMPwidget::SeekMode
1357 * \brief Seeking modes
1359 * This enumeration describes valid modes for seeking the media stream.
1362 * <tr><th>Constant</th><th>Value</th><th>Description</th></tr>
1364 * <td>\p QMPwidget::RelativeSeek</td>
1366 * <td>Relative seek in seconds</td>
1369 * <td>\p QMPwidget::PercantageSeek</td>
1371 * <td>Seek to a position given by a percentage of the whole movie duration</td>
1374 * <td>\p QMPwidget::AbsoluteSeek</td>
1376 * <td>Seek to a position given by an absolute time</td>
1382 * \fn void QMPwidget::stateChanged(int state)
1383 * \brief Emitted if the state has changed
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
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
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
1410 * This signal is emitted when MPlayer wrote a line of text to its standard error channel.