File indexing completed on 2024-05-12 04:52:07

0001 /*
0002  * mplayermediawidget.cpp
0003  *
0004  * Copyright (C) 2010-2011 Christoph Pfister <christophpfister@gmail.com>
0005  *
0006  * This program is free software; you can redistribute it and/or modify
0007  * it under the terms of the GNU General Public License as published by
0008  * the Free Software Foundation; either version 2 of the License, or
0009  * (at your option) any later version.
0010  *
0011  * This program is distributed in the hope that it will be useful,
0012  * but WITHOUT ANY WARRANTY; without even the implied warranty of
0013  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0014  * GNU General Public License for more details.
0015  *
0016  * You should have received a copy of the GNU General Public License along
0017  * with this program; if not, write to the Free Software Foundation, Inc.,
0018  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
0019  */
0020 
0021 #include <KLocalizedString>
0022 #include <KMessageBox>
0023 
0024 #include "mplayermediawidget.h"
0025 #include "mplayervideowidget.h"
0026 
0027 MPlayerMediaWidget::MPlayerMediaWidget(QWidget *parent) : AbstractMediaWidget(parent),
0028     muted(false), volume(0), aspectRatio(MediaWidget::AspectRatioAuto), deinterlacing(false),
0029     timerId(0), videoWidth(0), videoHeight(0), videoAspectRatio(1)
0030 {
0031     videoWidget = new MPlayerVideoWidget(this);
0032     standardError.open(stderr, QIODevice::WriteOnly);
0033     connect(&process, SIGNAL(error(QProcess::ProcessError)),
0034         this, SLOT(error(QProcess::ProcessError)));
0035     connect(&process, SIGNAL(readyReadStandardOutput()), this, SLOT(readStandardOutput()));
0036     connect(&process, SIGNAL(readyReadStandardError()), this, SLOT(readStandardError()));
0037     process.start(QString("mplayer -idle -osdlevel 0 -quiet -slave -softvol -vf yadif "
0038         "-volume 0 -wid %1").arg(videoWidget->winId()));
0039 }
0040 
0041 MPlayerMediaWidget::~MPlayerMediaWidget()
0042 {
0043     sendCommand(Quit);
0044     process.waitForFinished(10000);
0045 }
0046 
0047 AbstractMediaWidget *MPlayerMediaWidget::createMPlayerMediaWidget(QWidget *parent)
0048 {
0049     return new MPlayerMediaWidget(parent);
0050 }
0051 
0052 void MPlayerMediaWidget::setMuted(bool muted_)
0053 {
0054     muted = muted_;
0055     sendCommand(SetVolume);
0056 }
0057 
0058 void MPlayerMediaWidget::setVolume(int volume_)
0059 {
0060     volume = volume_;
0061     sendCommand(SetVolume);
0062 }
0063 
0064 void MPlayerMediaWidget::setAspectRatio(MediaWidget::AspectRatio aspectRatio_)
0065 {
0066     aspectRatio = aspectRatio_;
0067     updateVideoWidgetGeometry();
0068 }
0069 
0070 void MPlayerMediaWidget::setDeinterlacing(MediaWidget::DeinterlaceMode deinterlacing);
0071 
0072 {
0073     deinterlacing = deinterlacing_;
0074     sendCommand(SetDeinterlacing);
0075 }
0076 
0077 void MPlayerMediaWidget::play(const MediaSource &source)
0078 {
0079     resetState();
0080     QByteArray url = source.getUrl().toEncoded();
0081 
0082     switch (source.getType()) {
0083     case MediaSource::Url:
0084         if (url.endsWith(".iso")) {
0085             // FIXME use dvd://, dvdnav:// ?
0086             updateDvdMenu(true);
0087         }
0088 
0089         if (source.getUrl().isLocalFile()) {
0090             // mplayer can't deal with urls like "file:///tmp/te%20st.m2t"
0091             url = QFile::encodeName(source.getUrl().toLocalFile());
0092             url.replace(' ', "\\ ");
0093         }
0094 
0095         break;
0096     case MediaSource::AudioCd:
0097         if (url.size() >= 7) {
0098             // e.g. cdda:////dev/sr0
0099             url.replace(0, 5, "cdda:/");
0100         } else {
0101             url = "cdda://";
0102         }
0103 
0104         break;
0105     case MediaSource::VideoCd:
0106         if (url.size() >= 7) {
0107             // e.g. vcd:////dev/sr0
0108             url.replace(0, 5, "vcd:/");
0109         } else {
0110             url = "vcd://";
0111         }
0112 
0113         break;
0114     case MediaSource::Dvd:
0115         if (url.size() >= 7) {
0116             // e.g. dvdnav:////dev/sr0
0117             url.replace(0, 5, "dvdnav:/");
0118         } else {
0119             url = "dvdnav://";
0120         }
0121 
0122         updateDvdMenu(true);
0123         break;
0124     case MediaSource::Dvb:
0125         if (source.getUrl().isLocalFile()) {
0126             // mplayer can't deal with urls like "file:///tmp/te%20st.m2t"
0127             url = QFile::encodeName(source.getUrl().toLocalFile());
0128             url.replace(' ', "\\ ");
0129         }
0130 
0131         break;
0132     }
0133 
0134     updatePlaybackStatus(MediaWidget::Playing);
0135     updateSeekable(true);
0136     process.write("loadfile " + url + '\n');
0137     process.write("pausing_keep_force get_property path\n");
0138     sendCommand(SetDeinterlacing);
0139     sendCommand(SetVolume);
0140     timerId = startTimer(500);
0141 }
0142 
0143 void MPlayerMediaWidget::stop()
0144 {
0145     resetState();
0146     sendCommand(Stop);
0147 }
0148 
0149 void MPlayerMediaWidget::setPaused(bool paused)
0150 {
0151     switch (getPlaybackStatus()) {
0152     case MediaWidget::Idle:
0153         break;
0154     case MediaWidget::Playing:
0155         if (paused) {
0156             updatePlaybackStatus(MediaWidget::Paused);
0157             sendCommand(TogglePause);
0158         }
0159 
0160         break;
0161     case MediaWidget::Paused:
0162         if (!paused) {
0163             updatePlaybackStatus(MediaWidget::Playing);
0164             sendCommand(TogglePause);
0165         }
0166 
0167         break;
0168     }
0169 }
0170 
0171 void MPlayerMediaWidget::seek(int time)
0172 {
0173     process.write("pausing_keep_force set_property time_pos " +
0174         QByteArray::number(float(time) / 1000) + '\n');
0175 }
0176 
0177 void MPlayerMediaWidget::setCurrentAudioStream(int currentAudioStream)
0178 {
0179     if ((currentAudioStream >= 0) && (currentAudioStream < audioIds.size())) {
0180         process.write("pausing_keep_force set_property switch_audio " +
0181             QByteArray::number(audioIds.at(currentAudioStream)) +
0182             "\npausing_keep_force get_property switch_audio\n");
0183     }
0184 }
0185 
0186 void MPlayerMediaWidget::setCurrentSubtitle(int currentSubtitle)
0187 {
0188     process.write("pausing_keep_force set_property sub " +
0189         QByteArray::number(currentSubtitle) +
0190         "\npausing_keep_force get_property sub\n");
0191 }
0192 
0193 void MPlayerMediaWidget::setExternalSubtitle(const QUrl &subtitleUrl)
0194 {
0195     // FIXME
0196     Q_UNUSED(subtitleUrl)
0197 }
0198 
0199 void MPlayerMediaWidget::setCurrentTitle(int currentTitle)
0200 {
0201     process.write("pausing_keep_force set_property switch_title " +
0202         QByteArray::number(currentTitle) +
0203         "\npausing_keep_force get_property switch_title\n"
0204         "pausing_keep_force get_property chapter\n");
0205 }
0206 
0207 void MPlayerMediaWidget::setCurrentChapter(int currentChapter)
0208 {
0209     process.write("pausing_keep_force set_property chapter " +
0210         QByteArray::number(currentChapter) +
0211         "\npausing_keep_force get_property switch_title\n"
0212         "pausing_keep_force get_property chapter\n");
0213 }
0214 
0215 void MPlayerMediaWidget::setCurrentAngle(int currentAngle)
0216 {
0217     process.write("pausing_keep_force set_property switch_angle " +
0218         QByteArray::number(currentAngle) + '\n');
0219 }
0220 
0221 bool MPlayerMediaWidget::jumpToPreviousChapter()
0222 {
0223     if ((getCurrentChapter() - 1) >= 0) {
0224         setCurrentChapter(getCurrentChapter() - 1);
0225         return true;
0226     }
0227 
0228     if ((getCurrentTitle() - 1) >= 0) {
0229         setCurrentTitle(getCurrentTitle() - 1);
0230         return true;
0231     }
0232 
0233     return false;
0234 }
0235 
0236 bool MPlayerMediaWidget::jumpToNextChapter()
0237 {
0238     if ((getCurrentChapter() + 1) < getChapterCount()) {
0239         setCurrentChapter(getCurrentChapter() + 1);
0240         return true;
0241     }
0242 
0243     if ((getCurrentTitle() + 1) < getTitleCount()) {
0244         setCurrentTitle(getCurrentTitle() + 1);
0245         return true;
0246     }
0247 
0248     return false;
0249 }
0250 
0251 void MPlayerMediaWidget::showDvdMenu()
0252 {
0253     sendCommand(ShowDvdMenu);
0254 }
0255 
0256 void MPlayerMediaWidget::error(QProcess::ProcessError error)
0257 {
0258     Q_UNUSED(error)
0259     KMessageBox::queuedMessageBox(this, KMessageBox::Error,
0260         i18n("Cannot start mplayer process."));
0261 }
0262 
0263 void MPlayerMediaWidget::readStandardOutput()
0264 {
0265     QByteArray data = process.readAllStandardOutput();
0266     standardError.write(data); // forward
0267     standardError.flush();
0268 
0269     if ((data == "\n") || (data.indexOf("\n\n") >= 0)) {
0270         process.write("pausing_keep_force get_property path\n");
0271     }
0272 
0273     bool videoPropertiesChanged = false;
0274     QStringList audioStreams = getAudioStreams();
0275     bool audioStreamsChanged = false;
0276     QStringList subtitles = getSubtitles();
0277     bool subtitlesChanged = false;
0278 
0279     foreach (const QByteArray &line, data.split('\n')) {
0280         if (line.startsWith("VO: ")) {
0281             videoPropertiesChanged = true;
0282             continue;
0283         }
0284 
0285         if (line.startsWith("audio stream: ")) {
0286             int begin = 14;
0287             int end = line.indexOf(' ', begin);
0288 
0289             if (end < 0) {
0290                 end = line.size();
0291             }
0292 
0293             int audioStreamIndex = line.mid(begin, end - begin).toInt();
0294 
0295             while (audioStreams.size() < audioStreamIndex) {
0296                 audioStreams.append(QString::number(audioStreams.size() + 1));
0297             }
0298 
0299             while (audioIds.size() < audioStreamIndex) {
0300                 audioIds.append(-1);
0301             }
0302 
0303             audioStreams.erase(audioStreams.begin() + audioStreamIndex,
0304                 audioStreams.end());
0305             audioIds.erase(audioIds.begin() + audioStreamIndex, audioIds.end());
0306             QString audioStream;
0307             begin = line.indexOf("language: ");
0308 
0309             if (begin >= 0) {
0310                 begin += 10;
0311                 end = line.indexOf(' ', begin);
0312 
0313                 if (end < 0) {
0314                     end = line.size();
0315                 }
0316 
0317                 audioStream = line.mid(begin, end - begin);
0318             }
0319 
0320             if (audioStream.isEmpty()) {
0321                 audioStream = QString::number(audioStreams.size() + 1);
0322             }
0323 
0324             int audioId = -1;
0325             begin = line.indexOf("aid: ");
0326 
0327             if (begin >= 0) {
0328                 begin += 5;
0329                 end = line.indexOf('.', begin);
0330 
0331                 if (end < 0) {
0332                     end = line.size();
0333                 }
0334 
0335                 audioId = line.mid(begin, end - begin).toInt();
0336             }
0337 
0338             audioStreams.append(audioStream);
0339             audioIds.append(audioId);
0340             audioStreamsChanged = true;
0341             continue;
0342         }
0343 
0344         if (line.startsWith("subtitle ")) {
0345             int begin = line.indexOf("( sid ): ");
0346 
0347             if (begin < 0) {
0348                 continue;
0349             }
0350 
0351             begin += 9;
0352             int end = line.indexOf(' ', begin);
0353 
0354             if (end < 0) {
0355                 end = line.size();
0356             }
0357 
0358             int subtitleIndex = line.mid(begin, end - begin).toInt();
0359 
0360             while (subtitles.size() < subtitleIndex) {
0361                 subtitles.append(QString::number(subtitles.size() + 1));
0362             }
0363 
0364             subtitles.erase(subtitles.begin() + subtitleIndex, subtitles.end());
0365             QString subtitle;
0366             begin = line.indexOf("language: ");
0367 
0368             if (begin >= 0) {
0369                 begin += 10;
0370                 end = line.indexOf(' ', begin);
0371 
0372                 if (end < 0) {
0373                     end = line.size();
0374                 }
0375 
0376                 subtitle = line.mid(begin, end - begin);
0377             }
0378 
0379             if (subtitle.isEmpty()) {
0380                 subtitle = QString::number(subtitles.size() + 1);
0381             }
0382 
0383             subtitles.append(subtitle);
0384             subtitlesChanged = true;
0385             continue;
0386         }
0387 
0388         if (line == "ANS_path=(null)") {
0389             switch (getPlaybackStatus()) {
0390             case MediaWidget::Idle:
0391                 break;
0392             case MediaWidget::Playing:
0393             case MediaWidget::Paused:
0394                 playbackFinished();
0395                 break;
0396             }
0397 
0398             resetState();
0399             continue;
0400         }
0401 
0402         if (line.startsWith("ANS_length=")) {
0403             int totalTime = (line.mid(11).toFloat() * 1000 + 0.5);
0404             updateCurrentTotalTime(getCurrentTime(), totalTime);
0405             continue;
0406         }
0407 
0408         if (line.startsWith("ANS_time_pos=")) {
0409             int currentTime = (line.mid(13).toFloat() * 1000 + 0.5);
0410             updateCurrentTotalTime(currentTime, getTotalTime());
0411             continue;
0412         }
0413 
0414         if (line.startsWith("ANS_width=")) {
0415             videoWidth = line.mid(10).toInt();
0416 
0417             if (videoWidth < 0) {
0418                 videoWidth = 0;
0419             }
0420 
0421             continue;
0422         }
0423 
0424         if (line.startsWith("ANS_height=")) {
0425             videoHeight = line.mid(11).toInt();
0426 
0427             if (videoHeight < 0) {
0428                 videoHeight = 0;
0429             }
0430 
0431             continue;
0432         }
0433 
0434         if (line.startsWith("ANS_aspect=")) {
0435             videoAspectRatio = line.mid(11).toFloat();
0436 
0437             if ((videoAspectRatio > 0.01) && (videoAspectRatio < 100)) {
0438                 // ok
0439             } else {
0440                 videoAspectRatio = (videoWidth / float(videoHeight));
0441 
0442                 if ((videoAspectRatio > 0.01) && (videoAspectRatio < 100)) {
0443                     // ok
0444                 } else {
0445                     videoAspectRatio = 1;
0446                 }
0447             }
0448 
0449             updateVideoWidgetGeometry();
0450             continue;
0451         }
0452 
0453         if (line.startsWith("ANS_switch_audio=")) {
0454             int audioId = line.mid(17).toInt();
0455             updateCurrentAudioStream(audioIds.indexOf(audioId));
0456             continue;
0457         }
0458 
0459         if (line.startsWith("ANS_sub=")) {
0460             int currentSubtitle = line.mid(8).toInt();
0461             updateCurrentSubtitle(currentSubtitle);
0462             continue;
0463         }
0464     }
0465 
0466     if (videoPropertiesChanged) {
0467         process.write("pausing_keep_force get_property width\n"
0468             "pausing_keep_force get_property height\n"
0469             "pausing_keep_force get_property aspect\n");
0470     }
0471 
0472     if (audioStreamsChanged) {
0473         updateAudioStreams(audioStreams);
0474         process.write("pausing_keep_force get_property switch_audio\n");
0475     }
0476 
0477     if (subtitlesChanged) {
0478         updateSubtitles(subtitles);
0479         process.write("pausing_keep_force get_property sub\n");
0480     }
0481 }
0482 
0483 void MPlayerMediaWidget::readStandardError()
0484 {
0485     QByteArray data = process.readAllStandardError();
0486     standardError.write(data); // forward
0487     standardError.flush();
0488 }
0489 
0490 void MPlayerMediaWidget::mouseMoved(int x, int y)
0491 {
0492     process.write("set_mouse_pos " + QByteArray::number(x) + ' ' + QByteArray::number(y) +
0493         '\n');
0494 }
0495 
0496 void MPlayerMediaWidget::mouseClicked()
0497 {
0498     process.write("dvdnav mouse\n");
0499 }
0500 
0501 void MPlayerMediaWidget::resetState()
0502 {
0503     resetBaseState();
0504 
0505     if (timerId != 0) {
0506         killTimer(timerId);
0507         timerId = 0;
0508     }
0509 
0510     audioIds.clear();
0511     videoWidth = 0;
0512     videoHeight = 0;
0513     videoAspectRatio = 1;
0514     updateVideoWidgetGeometry();
0515 }
0516 
0517 void MPlayerMediaWidget::resizeEvent(QResizeEvent *event)
0518 {
0519     updateVideoWidgetGeometry();
0520     AbstractMediaWidget::resizeEvent(event);
0521 }
0522 
0523 void MPlayerMediaWidget::sendCommand(Command command)
0524 {
0525     switch (command) {
0526     case SetDeinterlacing:
0527         if (getPlaybackStatus() == MediaWidget::Idle) {
0528             // only works if media is loaded
0529             break;
0530         }
0531 
0532         process.write("pausing_keep_force set_property deinterlace %1\n", deinterlacing);
0533 
0534         break;
0535     case SetVolume: {
0536         if (getPlaybackStatus() == MediaWidget::Idle) {
0537             // only works if media is loaded
0538             break;
0539         }
0540 
0541         int realVolume = volume;
0542 
0543         if (muted) {
0544             realVolume = 0;
0545         }
0546 
0547         process.write("pausing_keep_force set_property volume " +
0548             QByteArray::number(realVolume) + '\n');
0549         break;
0550         }
0551     case ShowDvdMenu:
0552         process.write("dvdnav menu\n");
0553         break;
0554     case Stop:
0555         process.write("stop\n");
0556         break;
0557     case TogglePause:
0558         process.write("pause\n");
0559         break;
0560     case Quit:
0561         process.write("quit\n");
0562         break;
0563     }
0564 }
0565 
0566 void MPlayerMediaWidget::timerEvent(QTimerEvent *event)
0567 {
0568     Q_UNUSED(event)
0569     process.write("pausing_keep_force get_property length\n"
0570         "pausing_keep_force get_property time_pos\n");
0571 }
0572 
0573 void MPlayerMediaWidget::updateVideoWidgetGeometry()
0574 {
0575     float effectiveAspectRatio = videoAspectRatio;
0576 
0577     switch (aspectRatio) {
0578     case MediaWidget::AspectRatioAuto:
0579         break;
0580     case MediaWidget::AspectRatio1_1:
0581         effectiveAspectRatio = 1;
0582         break;
0583     case MediaWidget::AspectRatio4_3:
0584         effectiveAspectRatio = (4.0 / 3.0);
0585         break;
0586     case MediaWidget::AspectRatio5_4:
0587         effectiveAspectRatio = (5.0 / 4.0);
0588         break;
0589     case MediaWidget::AspectRatio16_9:
0590         effectiveAspectRatio = (16.0 / 9.0);
0591         break;
0592     case MediaWidget::AspectRatio16_10:
0593         effectiveAspectRatio = (16.0 / 10.0);
0594         break;
0595     case MediaWidget::AspectRatio221_100:
0596         effectiveAspectRatio = (221.0 / 100.0);
0597         break;
0598     case MediaWidget::AspectRatio235_100:
0599         effectiveAspectRatio = (235.0 / 100.0);
0600         break;
0601     case MediaWidget::AspectRatio239_100:
0602         effectiveAspectRatio = (239.0 / 100.0);
0603         break;
0604     }
0605 
0606     QRect geometry(QPoint(0, 0), size());
0607 
0608     if (getPlaybackStatus() == MediaWidget::Idle) {
0609         geometry.setSize(QSize(0, 0));
0610     } else if (effectiveAspectRatio > 0) {
0611         int newWidth = (geometry.height() * effectiveAspectRatio + 0.5);
0612 
0613         if (newWidth <= geometry.width()) {
0614             geometry.setX((geometry.width() - newWidth) / 2);
0615             geometry.setWidth(newWidth);
0616         } else {
0617             int newHeight = (geometry.width() / effectiveAspectRatio + 0.5);
0618             geometry.setY((geometry.height() - newHeight) / 2);
0619             geometry.setHeight(newHeight);
0620         }
0621     }
0622 
0623     if (videoWidget->geometry() != geometry) {
0624         videoWidget->setGeometry(geometry);
0625     }
0626 }
0627 
0628 #include "moc_mplayermediawidget.cpp"