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(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()));
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 if (!useFakeInputconf
) {
277 myargs
+= "nodefault-bindings:conf=/dev/null";
280 // Ugly hack for older versions of mplayer (used in kmplayer and other)
281 if (m_fakeInputconf
== NULL
) {
282 m_fakeInputconf
= new QTemporaryFile();
283 if (m_fakeInputconf
->open()) {
284 writeFakeInputconf(m_fakeInputconf
);
286 delete m_fakeInputconf
;
287 m_fakeInputconf
= NULL
;
290 if (m_fakeInputconf
!= NULL
) {
292 myargs
+= QString("conf=%1").arg(m_fakeInputconf
->fileName());
297 if (m_mode
== QMPwidget::EmbeddedMode
) {
299 myargs
+= QString::number((int)widget
->winId());
300 if (!m_videoOutput
.isEmpty()) {
302 myargs
+= m_videoOutput
;
305 #ifdef QMP_USE_YUVPIPE
307 myargs
+= QString("yuv4mpeg:file=%1").arg(m_yuvReader
->m_pipe
);
312 #ifdef QMP_DEBUG_OUTPUT
315 QProcess::start(m_mplayerPath
, myargs
);
316 changeState(QMPwidget::IdleState
);
318 if (m_mode
== QMPwidget::PipeMode
) {
319 #ifdef QMP_USE_YUVPIPE
320 connect(m_yuvReader
, SIGNAL(imageReady(const QImage
&)), widget
, SLOT(displayImage(const QImage
&)));
321 m_yuvReader
->start();
326 QString
mplayerVersion()
329 p
.start(m_mplayerPath
, QStringList("-version"));
330 if (!p
.waitForStarted()) {
333 if (!p
.waitForFinished()) {
337 QString output
= QString(p
.readAll());
338 QRegExp
re("MPlayer ([^ ]*)");
339 if (re
.indexIn(output
) > -1) {
345 QProcess::ProcessState
processState() const
347 return QProcess::state();
350 void writeCommand(const QString
&command
)
352 #ifdef QMP_DEBUG_OUTPUT
353 qDebug("in: \"%s\"", qPrintable(command
));
355 QProcess::write(command
.toLocal8Bit()+"\n");
360 writeCommand("quit");
361 QProcess::waitForFinished(100);
362 if (QProcess::state() == QProcess::Running
) {
365 QProcess::waitForFinished(-1);
370 writeCommand("pause");
375 changeState(QMPwidget::StoppedState
);
376 writeCommand("stop");
380 void stateChanged(int state
);
381 void streamPositionChanged(double position
);
382 void error(const QString
&reason
);
384 void readStandardOutput(const QString
&line
);
385 void readStandardError(const QString
&line
);
390 QStringList lines
= QString::fromLocal8Bit(readAllStandardOutput()).split("\n", QString::SkipEmptyParts
);
391 for (int i
= 0; i
< lines
.count(); i
++) {
392 lines
[i
].remove("\r");
393 #ifdef QMP_DEBUG_OUTPUT
394 qDebug("out: \"%s\"", qPrintable(lines
[i
]));
397 emit
readStandardOutput(lines
[i
]);
403 QStringList lines
= QString::fromLocal8Bit(readAllStandardError()).split("\n", QString::SkipEmptyParts
);
404 for (int i
= 0; i
< lines
.count(); i
++) {
405 lines
[i
].remove("\r");
406 #ifdef QMP_DEBUG_OUTPUT
407 qDebug("err: \"%s\"", qPrintable(lines
[i
]));
410 emit
readStandardError(lines
[i
]);
416 // Called if the *process* has finished
417 changeState(QMPwidget::NotStartedState
);
422 if (m_state
== QMPwidget::PlayingState
) {
423 changeState(QMPwidget::IdleState
);
428 // Parses a line of MPlayer output
429 void parseLine(const QString
&line
)
431 if (line
.startsWith("Playing ")) {
432 changeState(QMPwidget::LoadingState
);
433 } else if (line
.startsWith("Cache fill:")) {
434 changeState(QMPwidget::BufferingState
);
435 } else if (line
.startsWith("Starting playback...")) {
436 m_mediaInfo
.ok
= true; // No more info here
437 changeState(QMPwidget::PlayingState
);
438 } else if (line
.startsWith("File not found: ")) {
439 changeState(QMPwidget::ErrorState
);
440 } else if (line
.endsWith("ID_PAUSED")) {
441 changeState(QMPwidget::PausedState
);
442 } else if (line
.startsWith("ID_")) {
443 parseMediaInfo(line
);
444 } else if (line
.startsWith("No stream found")) {
445 changeState(QMPwidget::ErrorState
, line
);
446 } else if (line
.startsWith("A:") || line
.startsWith("V:")) {
447 if (m_state
!= QMPwidget::PlayingState
448 && m_state
!= QMPwidget::StoppedState
) {
449 changeState(QMPwidget::PlayingState
);
452 } else if (line
.startsWith("Exiting...")) {
453 changeState(QMPwidget::NotStartedState
);
457 // Parses MPlayer's media identification output
458 void parseMediaInfo(const QString
&line
)
460 QStringList info
= line
.split("=");
461 if (info
.count() < 2) {
465 if (info
[0] == "ID_VIDEO_FORMAT") {
466 m_mediaInfo
.videoFormat
= info
[1];
467 } else if (info
[0] == "ID_VIDEO_BITRATE") {
468 m_mediaInfo
.videoBitrate
= info
[1].toInt();
469 } else if (info
[0] == "ID_VIDEO_WIDTH") {
470 m_mediaInfo
.size
.setWidth(info
[1].toInt());
471 } else if (info
[0] == "ID_VIDEO_HEIGHT") {
472 m_mediaInfo
.size
.setHeight(info
[1].toInt());
473 } else if (info
[0] == "ID_VIDEO_FPS") {
474 m_mediaInfo
.framesPerSecond
= info
[1].toDouble();
476 } else if (info
[0] == "ID_AUDIO_FORMAT") {
477 m_mediaInfo
.audioFormat
= info
[1];
478 } else if (info
[0] == "ID_AUDIO_BITRATE") {
479 m_mediaInfo
.audioBitrate
= info
[1].toInt();
480 } else if (info
[0] == "ID_AUDIO_RATE") {
481 m_mediaInfo
.sampleRate
= info
[1].toInt();
482 } else if (info
[0] == "ID_AUDIO_NCH") {
483 m_mediaInfo
.numChannels
= info
[1].toInt();
485 } else if (info
[0] == "ID_LENGTH") {
486 m_mediaInfo
.length
= info
[1].toDouble();
487 } else if (info
[0] == "ID_SEEKABLE") {
488 m_mediaInfo
.seekable
= (bool)info
[1].toInt();
490 } else if (info
[0].startsWith("ID_CLIP_INFO_NAME")) {
491 m_currentTag
= info
[1];
492 } else if (info
[0].startsWith("ID_CLIP_INFO_VALUE") && !m_currentTag
.isEmpty()) {
493 m_mediaInfo
.tags
.insert(m_currentTag
, info
[1]);
497 // Parsas MPlayer's position output
498 void parsePosition(const QString
&line
)
500 static QRegExp
rx("[ :]");
501 QStringList info
= line
.split(rx
, QString::SkipEmptyParts
);
503 double oldpos
= m_streamPosition
;
504 for (int i
= 0; i
< info
.count(); i
++) {
505 if (info
[i
] == "V" && info
.count() > i
) {
506 m_streamPosition
= info
[i
+1].toDouble();
508 // If the movie is near its end, start a timer that will check whether
509 // the movie has really finished.
510 if (qAbs(m_streamPosition
- m_mediaInfo
.length
) < 1) {
511 m_movieFinishedTimer
.start();
516 if (oldpos
!= m_streamPosition
) {
517 emit
streamPositionChanged(m_streamPosition
);
521 // Changes the current state, possibly emitting multiple signals
522 void changeState(QMPwidget::State state
, const QString
&comment
= QString())
524 #ifdef QMP_USE_YUVPIPE
525 if (m_yuvReader
!= NULL
&& (state
== QMPwidget::ErrorState
|| state
== QMPwidget::NotStartedState
)) {
527 m_yuvReader
->deleteLater();
531 if (m_state
== state
) {
535 if (m_state
== QMPwidget::PlayingState
) {
536 m_movieFinishedTimer
.stop();
540 emit
stateChanged(m_state
);
543 case QMPwidget::NotStartedState
:
547 case QMPwidget::ErrorState
:
556 // Resets the media info and position values
559 m_mediaInfo
= QMPwidget::MediaInfo();
560 m_streamPosition
= -1;
563 // Writes a dummy input configuration to the given device
564 void writeFakeInputconf(QIODevice
*device
)
566 // Query list of supported keys
568 p
.start(m_mplayerPath
, QStringList("-input") += "keylist");
569 if (!p
.waitForStarted()) {
572 if (!p
.waitForFinished()) {
575 QStringList keys
= QString(p
.readAll()).split("\n", QString::SkipEmptyParts
);
577 // Write dummy command for each key
578 QTextStream
out(device
);
579 for (int i
= 0; i
< keys
.count(); i
++) {
580 keys
[i
].remove("\r");
581 out
<< keys
[i
] << " " << "ignored" << endl
;
586 QMPwidget::State m_state
;
588 QString m_mplayerPath
;
589 QString m_videoOutput
;
591 QMPwidget::Mode m_mode
;
593 QMPwidget::MediaInfo m_mediaInfo
;
594 double m_streamPosition
; // This is the video position
595 QTimer m_movieFinishedTimer
;
597 QString m_currentTag
;
599 QTemporaryFile
*m_fakeInputconf
;
601 #ifdef QMP_USE_YUVPIPE
602 QPointer
<QMPYuvReader
> m_yuvReader
;
607 // Initialize the media info structure
608 QMPwidget::MediaInfo::MediaInfo()
609 : videoBitrate(0), framesPerSecond(0), sampleRate(0), numChannels(0),
610 ok(false), length(0), seekable(false)
619 * \param parent Parent widget
621 QMPwidget::QMPwidget(QWidget
*parent
)
624 setFocusPolicy(Qt::StrongFocus
);
625 setSizePolicy(QSizePolicy::Expanding
, QSizePolicy::Expanding
);
628 m_widget
= new QMPOpenGLVideoWidget(this);
630 m_widget
= new QMPPlainVideoWidget(this);
633 QPalette p
= palette();
634 p
.setColor(QPalette::Window
, Qt::black
);
637 m_seekTimer
.setInterval(50);
638 m_seekTimer
.setSingleShot(true);
639 connect(&m_seekTimer
, SIGNAL(timeout()), this, SLOT(delayedSeek()));
641 m_process
= new QMPProcess(this);
642 connect(m_process
, SIGNAL(stateChanged(int)), this, SLOT(mpStateChanged(int)));
643 connect(m_process
, SIGNAL(streamPositionChanged(double)), this, SLOT(mpStreamPositionChanged(double)));
644 connect(m_process
, SIGNAL(error(const QString
&)), this, SIGNAL(error(const QString
&)));
645 connect(m_process
, SIGNAL(readStandardOutput(const QString
&)), this, SIGNAL(readStandardOutput(const QString
&)));
646 connect(m_process
, SIGNAL(readStandardError(const QString
&)), this, SIGNAL(readStandardError(const QString
&)));
652 * This function will ask the MPlayer process to quit and block until it has really
655 QMPwidget::~QMPwidget()
657 if (m_process
->processState() == QProcess::Running
) {
664 * \brief Returns the current MPlayer process state
666 * \returns The process state
668 QMPwidget::State
QMPwidget::state() const
670 return m_process
->m_state
;
674 * \brief Returns the current media info object
676 * Please check QMPwidget::MediaInfo::ok to make sure the media
677 * information has been fully parsed.
679 * \returns The media info object
681 QMPwidget::MediaInfo
QMPwidget::mediaInfo() const
683 return m_process
->m_mediaInfo
;
687 * \brief Returns the current playback position
689 * \returns The current playback position in seconds
692 double QMPwidget::tell() const
694 return m_process
->m_streamPosition
;
698 * \brief Returns the MPlayer process
700 * \returns The MPlayer process
702 QProcess
*QMPwidget::process() const
708 * \brief Sets the video playback mode
710 * Please see \ref playbackmodes for a discussion of the available modes.
712 * \param mode The video playback mode
715 void QMPwidget::setMode(Mode mode
)
717 #ifdef QMP_USE_YUVPIPE
718 m_process
->m_mode
= mode
;
725 * \brief Returns the current video playback mode
727 * \returns The current video playback mode
730 QMPwidget::Mode
QMPwidget::mode() const
732 return m_process
->m_mode
;
736 * \brief Sets the video output mode
738 * The video output mode string will be passed to MPlayer using its \p -vo option.
739 * Please see http://www.mplayerhq.hu/DOCS/HTML/en/video.html for an overview of
740 * available video output modes.
742 * Per default, this string will have the following values:
744 * <tr><th>System</th><th>Configuration</th><th>Value</th></tr>
748 * <td>\p "directx,directx:noaccel"</td>
752 * <td>Compiled without OpenGL support</td>
757 * <td>Compiled with OpenGL support</td>
758 * <td>\p "gl2,gl,xv"</td>
762 * <td>Compiled without OpenGL support</td>
763 * <td>\p "quartz"</td>
767 * <td>Compiled with OpenGL support</td>
768 * <td>\p "gl,quartz"</td>
773 * \param output The video output mode string
776 void QMPwidget::setVideoOutput(const QString
&output
)
778 m_process
->m_videoOutput
= output
;
782 * \brief Returns the current video output mode
784 * \returns The current video output mode
785 * \sa setVideoOutput()
787 QString
QMPwidget::videoOutput() const
789 return m_process
->m_videoOutput
;
793 * \brief Sets the path to the MPlayer executable
795 * Per default, it is assumed the MPlayer executable is
796 * available in the current OS path. Therefore, this value is
799 * \param path Path to the MPlayer executable
802 void QMPwidget::setMPlayerPath(const QString
&path
)
804 m_process
->m_mplayerPath
= path
;
808 * \brief Returns the current path to the MPlayer executable
810 * \returns The path to the MPlayer executable
811 * \sa setMPlayerPath()
813 QString
QMPwidget::mplayerPath() const
815 return m_process
->m_mplayerPath
;
819 * \brief Returns the version string of the MPlayer executable
824 * \returns The version string of the MPlayer executable
826 QString
QMPwidget::mplayerVersion()
828 return m_process
->mplayerVersion();
832 * \brief Sets a seeking slider for this widget
834 void QMPwidget::setSeekSlider(QAbstractSlider
*slider
)
837 m_seekSlider
->disconnect(this);
838 disconnect(m_seekSlider
);
841 if (m_process
->m_mediaInfo
.ok
) {
842 slider
->setRange(0, m_process
->m_mediaInfo
.length
);
844 if (m_process
->m_mediaInfo
.ok
) {
845 slider
->setEnabled(m_process
->m_mediaInfo
.seekable
);
848 connect(slider
, SIGNAL(valueChanged(int)), this, SLOT(seek(int)));
849 m_seekSlider
= slider
;
853 * \brief Sets a volume slider for this widget
855 void QMPwidget::setVolumeSlider(QAbstractSlider
*slider
)
857 if (m_volumeSlider
) {
858 m_volumeSlider
->disconnect(this);
859 disconnect(m_volumeSlider
);
863 slider
->setRange(0, 100);
864 slider
->setValue(100); // TODO
866 connect(slider
, SIGNAL(valueChanged(int)), this, SLOT(setVolume(int)));
867 m_volumeSlider
= slider
;
871 * \brief Shows a custom image
873 * This function sets a custom image that will be shown instead of the MPlayer
874 * video output. In order to show MPlayer's output again, call this function
877 * \note If the current playback mode is not set to \p PipeMode, this function
878 * will have no effect if MPlayer draws to the widget.
880 * \param image Custom image
882 void QMPwidget::showImage(const QImage
&image
)
885 qobject_cast
<QMPOpenGLVideoWidget
*>(m_widget
)->showUserImage(image
);
887 qobject_cast
<QMPPlainVideoWidget
*>(m_widget
)->showUserImage(image
);
892 * \brief Returns a suitable size hint for this widget
894 * This function is used internally by Qt.
896 QSize
QMPwidget::sizeHint() const
898 if (m_process
->m_mediaInfo
.ok
&& !m_process
->m_mediaInfo
.size
.isNull()) {
899 return m_process
->m_mediaInfo
.size
;
901 return QWidget::sizeHint();
905 * \brief Starts the MPlayer process with the given arguments
907 * If there's another process running, it will be terminated first. MPlayer
908 * will be run in idle mode and is avaiting your commands, e.g. via load().
910 * \param args MPlayer command line arguments
912 void QMPwidget::start(const QStringList
&args
)
914 if (m_process
->processState() == QProcess::Running
) {
917 m_process
->start(m_widget
, args
);
921 * \brief Loads a file or url and starts playback
923 * \param url File patho or url
925 void QMPwidget::load(const QString
&url
)
927 Q_ASSERT_X(m_process
->state() != QProcess::NotRunning
, "QMPwidget::load()", "MPlayer process not started yet");
929 // From the MPlayer slave interface documentation:
930 // "Try using something like [the following] to switch to the next file.
931 // It avoids audio playback starting to play the old file for a short time
932 // before switching to the new one.
933 writeCommand("pausing_keep_force pt_step 1");
934 writeCommand("get_property pause");
936 if (url
.endsWith(".txt", Qt::CaseInsensitive
))
937 writeCommand(QString("loadlist '%1'").arg(url
));
939 writeCommand(QString("loadfile '%1'").arg(url
));
943 * \brief Resumes playback
945 void QMPwidget::play()
947 if (m_process
->m_state
== PausedState
) {
953 * \brief Pauses playback
955 void QMPwidget::pause()
957 if (m_process
->m_state
== PlayingState
) {
963 * \brief Stops playback
965 void QMPwidget::stop()
971 * \brief Media playback seeking
973 * \param offset Seeking offset in seconds
974 * \param whence Seeking mode
975 * \returns \p true If the seeking mode is valid
978 bool QMPwidget::seek(int offset
, int whence
)
980 return seek(double(offset
), whence
);
984 * \brief Media playback seeking
986 * \param offset Seeking offset in seconds
987 * \param whence Seeking mode
988 * \returns \p true If the seeking mode is valid
991 bool QMPwidget::seek(double offset
, int whence
)
993 m_seekTimer
.stop(); // Cancel all current seek requests
1004 // Schedule seek request
1005 m_seekCommand
= QString("seek %1 %2").arg(offset
).arg(whence
);
1006 m_seekTimer
.start();
1011 * \brief Toggles full-screen mode
1013 void QMPwidget::toggleFullScreen()
1015 if (!isFullScreen()) {
1016 m_windowFlags
= windowFlags() & (Qt::Window
);
1017 m_geometry
= geometry();
1018 setWindowFlags((windowFlags() | Qt::Window
));
1019 // From Phonon::VideoWidget
1023 setWindowState(windowState() | Qt::WindowFullScreen
);
1025 setWindowState(windowState() | Qt::WindowFullScreen
);
1029 setWindowFlags((windowFlags() ^ (Qt::Window
)) | m_windowFlags
);
1030 setWindowState(windowState() & ~Qt::WindowFullScreen
);
1031 setGeometry(m_geometry
);
1037 * \brief Sends a command to the MPlayer process
1039 * Since MPlayer is being run in slave mode, it reads commands from the standard
1040 * input. It is assumed that the interface provided by this class might not be
1041 * sufficient for some situations, so you can use this functions to directly
1042 * control the MPlayer process.
1044 * For a complete list of commands for MPlayer's slave mode, see
1045 * http://www.mplayerhq.hu/DOCS/tech/slave.txt .
1047 * \param command The command line. A newline character will be added internally.
1049 void QMPwidget::writeCommand(const QString
&command
)
1051 m_process
->writeCommand(command
);
1055 * \brief Mouse double click event handler
1057 * This implementation will toggle full screen and accept the event
1059 * \param event Mouse event
1061 void QMPwidget::mouseDoubleClickEvent(QMouseEvent
*event
)
1068 * \brief Keyboard press event handler
1070 * This implementation tries to resemble the classic MPlayer interface. For a
1071 * full list of supported key codes, see \ref shortcuts.
1073 * \param event Key event
1075 void QMPwidget::keyPressEvent(QKeyEvent
*event
)
1078 switch (event
->key()) {
1081 if (state() == PlayingState
) {
1083 } else if (state() == PausedState
) {
1093 case Qt::Key_Escape
:
1098 writeCommand("audio_delay -0.1");
1101 writeCommand("audio_delay 0.1");
1105 seek(-10, RelativeSeek
);
1108 seek(10, RelativeSeek
);
1111 seek(-60, RelativeSeek
);
1114 seek(60, RelativeSeek
);
1116 case Qt::Key_PageDown
:
1117 seek(-600, RelativeSeek
);
1119 case Qt::Key_PageUp
:
1120 seek(600, RelativeSeek
);
1123 case Qt::Key_Asterisk
:
1124 writeCommand("volume 10");
1127 writeCommand("volume -10");
1131 writeCommand("sub_delay 0.1");
1134 writeCommand("sub_delay -0.1");
1142 event
->setAccepted(accept
);
1146 * \brief Resize event handler
1148 * If you reimplement this function, you need to call this handler, too.
1150 * \param event Resize event
1152 void QMPwidget::resizeEvent(QResizeEvent
*event
)
1158 void QMPwidget::updateWidgetSize()
1160 if (!m_process
->m_mediaInfo
.size
.isNull()) {
1161 QSize mediaSize
= m_process
->m_mediaInfo
.size
;
1162 QSize widgetSize
= size();
1164 double factor
= qMin(double(widgetSize
.width()) / mediaSize
.width(), double(widgetSize
.height()) / mediaSize
.height());
1165 QRect
wrect(0, 0, int(factor
* mediaSize
.width() + 0.5), int(factor
* mediaSize
.height()));
1166 wrect
.moveTopLeft(rect().center() - wrect
.center());
1167 m_widget
->setGeometry(wrect
);
1169 m_widget
->setGeometry(QRect(QPoint(0, 0), size()));
1174 void QMPwidget::delayedSeek()
1176 if (!m_seekCommand
.isEmpty()) {
1177 writeCommand(m_seekCommand
);
1178 m_seekCommand
= QString();
1182 void QMPwidget::setVolume(int volume
)
1184 writeCommand(QString("volume %1 1").arg(volume
));
1187 void QMPwidget::mpStateChanged(int state
)
1189 if (m_seekSlider
!= NULL
&& state
== PlayingState
&& m_process
->m_mediaInfo
.ok
) {
1190 m_seekSlider
->setRange(0, m_process
->m_mediaInfo
.length
);
1191 m_seekSlider
->setEnabled(m_process
->m_mediaInfo
.seekable
);
1195 emit
stateChanged(state
);
1198 void QMPwidget::mpStreamPositionChanged(double position
)
1200 if (m_seekSlider
!= NULL
&& m_seekCommand
.isEmpty() && m_seekSlider
->value() != qRound(position
)) {
1201 m_seekSlider
->disconnect(this);
1202 m_seekSlider
->setValue(qRound(position
));
1203 connect(m_seekSlider
, SIGNAL(valueChanged(int)), this, SLOT(seek(int)));
1207 void QMPwidget::mpVolumeChanged(int volume
)
1209 if (m_volumeSlider
!= NULL
) {
1210 m_volumeSlider
->disconnect(this);
1211 m_volumeSlider
->setValue(volume
);
1212 connect(m_seekSlider
, SIGNAL(valueChanged(int)), this, SLOT(setVolume(int)));
1217 #include "qmpwidget.moc"
1220 /* Documentation follows */
1224 * \brief A Qt widget for embedding MPlayer
1229 * \subsection comm MPlayer communication
1231 * If you want to communicate with MPlayer through its
1232 * <a href="http://www.mplayerhq.hu/DOCS/tech/slave.txt">slave mode protocol</a>,
1233 * you can use the writeCommand() slot. If MPlayer writes to its standard output
1234 * or standard error channel, the signals readStandardOutput() and
1235 * readStandardError() will be emitted.
1237 * \subsection controls Graphical controls
1239 * You can connect sliders for seeking and volume adjustment to an instance of
1240 * this class. Please use setSeekSlider() and setVolumeSlider(), respectively.
1242 * \section example Usage example
1244 * A minimal example using this widget to play a low-res version of
1245 * <a href="http://www.bigbuckbunny.org/">Big Buck Bunny</a> might look as follows.
1246 * Please note that the actual movie URL has been shortened for the sake of clarity.
1248 #include <QApplication>
1249 #include "qmpwidget.h"
1251 // Program entry point
1252 int main(int argc, char **argv)
1254 QApplication app(argc, argv);
1258 widget.start(QStringList("http://tinyurl.com/2vs2kg5"));
1265 * For further information about this project, please refer to the
1266 * <a href="index.html">main page</a>.
1270 * \enum QMPwidget::State
1271 * \brief MPlayer state
1273 * This enumeration is somewhat identical to <a href="http://doc.trolltech.com/phonon.html#State-enum">
1274 * Phonon's State enumeration</a>, except that it has an additional
1275 * member which is used when the MPlayer process has not been started yet (NotStartedState)
1278 * <tr><th>Constant</th><th>Value</th><th>Description</th></tr>
1280 * <td>\p QMPwidget::NotStartedState</td>
1282 * <td>The Mplayer process has not been started yet or has already terminated.</td>
1285 * <td>\p QMPwidget::IdleState</td>
1287 * <td>The MPlayer process has been started, but is idle and waiting for commands.</td>
1290 * <td>\p QMPwidget::LoadingState</td>
1292 * <td>The media file is being loaded, but playback has not been started yet.</td>
1295 * <td>\p QMPwidget::StoppedState</td>
1297 * <td>This constant is deprecated and is not being used</td>
1300 * <td>\p QMPwidget::PlayingState</td>
1305 * <td>\p QMPwidget::BufferingState</td>
1310 * <td>\p QMPwidget::PausedState</td>
1315 * <td>\p QMPwidget::ErrorState</td>
1323 * \enum QMPwidget::Mode
1324 * \brief Video playback modes
1326 * This enumeration describes valid modes for video playback. Please see \ref playbackmodes for a
1327 * detailed description of both modes.
1330 * <tr><th>Constant</th><th>Value</th><th>Description</th></tr>
1332 * <td>\p QMPwidget::EmbeddedMode</td>
1334 * <td>MPlayer will render directly into a Qt widget.</td>
1337 * <td>\p QMPwidget::PipedMode</td>
1339 * <td>MPlayer will write the video data into a FIFO which will be parsed in a seperate thread.\n
1340 The frames will be rendered by QMPwidget.</td>
1346 * \enum QMPwidget::SeekMode
1347 * \brief Seeking modes
1349 * This enumeration describes valid modes for seeking the media stream.
1352 * <tr><th>Constant</th><th>Value</th><th>Description</th></tr>
1354 * <td>\p QMPwidget::RelativeSeek</td>
1356 * <td>Relative seek in seconds</td>
1359 * <td>\p QMPwidget::PercantageSeek</td>
1361 * <td>Seek to a position given by a percentage of the whole movie duration</td>
1364 * <td>\p QMPwidget::AbsoluteSeek</td>
1366 * <td>Seek to a position given by an absolute time</td>
1372 * \fn void QMPwidget::stateChanged(int state)
1373 * \brief Emitted if the state has changed
1375 * This signal is emitted when the state of the MPlayer process changes.
1377 * \param state The new state
1381 * \fn void QMPwidget::error(const QString &reason)
1382 * \brief Emitted if the state has changed to QMPwidget::ErrorState
1384 * This signal is emitted when the state of the MPlayer process changes to QMPwidget::ErrorState.
1386 * \param reason Textual error description (may be empty)
1390 * \fn void QMPwidget::readStandardOutput(const QString &line)
1391 * \brief Signal for reading MPlayer's standard output
1393 * This signal is emitted when MPlayer wrote a line of text to its standard output channel.
1397 * \fn void QMPwidget::readStandardError(const QString &line)
1398 * \brief Signal for reading MPlayer's standard error
1400 * This signal is emitted when MPlayer wrote a line of text to its standard error channel.