File indexing completed on 2024-05-12 05:46:48

0001 /* This file is part of the KDE project
0002  *
0003  * Copyright (C) 2003 Koos Vriezen <koos.vriezen@xs4all.nl>
0004  *
0005  * This library is free software; you can redistribute it and/or
0006  * modify it under the terms of the GNU Library General Public
0007  * License as published by the Free Software Foundation; either
0008  * version 2 of the License, or (at your option) any later version.
0009  *
0010  * This library is distributed in the hope that it will be useful,
0011  * but WITHOUT ANY WARRANTY; without even the implied warranty of
0012  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0013  * Library General Public License for more details.
0014  *
0015  * You should have received a copy of the GNU Library General Public License
0016  * along with this library; see the file COPYING.LIB.  If not, write to
0017  * the Free Software Foundation, Inc., 51 Franklin Steet, Fifth Floor,
0018  * Boston, MA 02110-1301, USA.
0019  */
0020 #include <math.h>
0021 #include "config-kmplayer.h"
0022 #include <unistd.h>
0023 #include <qstring.h>
0024 #include <qfile.h>
0025 #include <qfileinfo.h>
0026 #include <qtimer.h>
0027 #include <qlayout.h>
0028 #include <qtablewidget.h>
0029 #include <qlineedit.h>
0030 #include <qslider.h>
0031 #include <qcombobox.h>
0032 #include <qcheckbox.h>
0033 #include <qspinbox.h>
0034 #include <qlabel.h>
0035 #include <qfontmetrics.h>
0036 #include <qwhatsthis.h>
0037 #include <QList>
0038 #include <QtDBus/QtDBus>
0039 #include <QtCore/QDir>
0040 #include <QtCore/QUrl>
0041 #include <QHeaderView>
0042 #include <QNetworkCookie>
0043 
0044 #include <kprotocolmanager.h>
0045 #include <kmessagebox.h>
0046 #include <klocalizedstring.h>
0047 #include <kshell.h>
0048 #include <kio/job.h>
0049 #include <kio/accessmanager.h>
0050 
0051 #include "kmplayercommon_log.h"
0052 #include "kmplayerconfig.h"
0053 #include "kmplayerview.h"
0054 #include "kmplayercontrolpanel.h"
0055 #include "kmplayerprocess.h"
0056 #include "kmplayerpartbase.h"
0057 #include "masteradaptor.h"
0058 #include "streammasteradaptor.h"
0059 #ifdef KMPLAYER_WITH_NPP
0060 # include "callbackadaptor.h"
0061 # include "streamadaptor.h"
0062 #endif
0063 
0064 using namespace KMPlayer;
0065 
0066 ProcessInfo::ProcessInfo (const char *nm, const QString &lbl,
0067         const char **supported, MediaManager* mgr, PreferencesPage *prefs)
0068  : name (nm),
0069    label (lbl),
0070    supported_sources (supported),
0071    manager (mgr),
0072    config_page (prefs) {
0073     if (config_page)
0074         manager->player ()->settings ()->addPage (config_page);
0075 }
0076 
0077 ProcessInfo::~ProcessInfo () {
0078     delete config_page;
0079 }
0080 
0081 bool ProcessInfo::supports (const char *source) const {
0082     for (const char ** s = supported_sources; s[0]; ++s) {
0083         if (!strcmp (s[0], source))
0084             return true;
0085     }
0086     return false;
0087 }
0088 
0089 //------------------------%<----------------------------------------------------
0090 
0091 static QString getPath (const KUrl & url) {
0092     QString p = QUrl::fromPercentEncoding (url.url ().toAscii ());
0093     if (p.startsWith (QString ("file:/"))) {
0094         int i = 0;
0095         p = p.mid (5);
0096         for (; i < p.size () && p[i] == QChar ('/'); ++i)
0097             ;
0098         //qCDebug(LOG_KMPLAYER_COMMON) << "getPath " << p.mid (i-1);
0099         if (i > 0)
0100             return p.mid (i-1);
0101         return QString (QChar ('/') + p);
0102     }
0103     return p;
0104 }
0105 
0106 static QString encodeFileOrUrl (const KUrl &url)
0107 {
0108     return url.isEmpty ()
0109         ? QString ()
0110         : QString::fromLocal8Bit (QFile::encodeName (
0111                     url.isLocalFile ()
0112                     ? url.toLocalFile ()
0113                     : QUrl::fromPercentEncoding (url.url ().toLocal8Bit ())));
0114 }
0115 
0116 static QString encodeFileOrUrl (const QString &str)
0117 {
0118     if (!str.startsWith (QString ("dvd:")) &&
0119             !str.startsWith (QString ("vcd:")) &&
0120             !str.startsWith (QString ("tv:")) &&
0121             !str.startsWith (QString ("cdda:")))
0122         return encodeFileOrUrl (KUrl (str));
0123     return str;
0124 }
0125 
0126 static void setupProcess (QProcess **process)
0127 {
0128     delete *process;
0129     *process = new QProcess;
0130     QStringList env = (*process)->systemEnvironment ();
0131     const QStringList::iterator e = env.end ();
0132     for (QStringList::iterator i = env.begin (); i != e; ++i)
0133         if ((*i).startsWith ("SESSION_MANAGER")) {
0134             env.erase (i);
0135             break;
0136         }
0137     (*process)->setEnvironment (env);
0138 }
0139 
0140 static void killProcess (QProcess *process, QWidget *widget) {
0141     if (!process || !process->pid ())
0142         return;
0143     process->terminate ();
0144     if (!process->waitForFinished (1000)) {
0145         process->kill ();
0146         if (!process->waitForFinished (1000) && widget)
0147             KMessageBox::error (widget,
0148                     i18n ("Failed to end player process."), i18n ("Error"));
0149     }
0150 }
0151 
0152 static void outputToView (View *view, const QByteArray &ba)
0153 {
0154     if (view && ba.size ())
0155         view->addText (QString::fromLocal8Bit (ba.constData ()));
0156 }
0157 
0158 Process::Process (QObject *parent, ProcessInfo *pinfo, Settings *settings)
0159  : QObject (parent),
0160    IProcess (pinfo),
0161    m_source (nullptr),
0162    m_settings (settings),
0163    m_old_state (IProcess::NotRunning),
0164    m_process (nullptr),
0165    m_job(nullptr),
0166    m_process_state (QProcess::NotRunning)
0167 {}
0168 
0169 Process::~Process () {
0170     quit ();
0171     delete m_process;
0172     if (user)
0173         user->processDestroyed (this);
0174 }
0175 
0176 void Process::init () {
0177 }
0178 
0179 void Process::initProcess () {
0180     setupProcess (&m_process);
0181     m_process_state = QProcess::NotRunning;
0182     connect (m_process, SIGNAL (stateChanged (QProcess::ProcessState)),
0183             this, SLOT (processStateChanged (QProcess::ProcessState)));
0184     if (m_source) m_source->setPosition (0);
0185 }
0186 
0187 WId Process::widget () {
0188     return view () && user && user->viewer ()
0189         ? user->viewer ()->windowHandle ()
0190         : 0;
0191 }
0192 
0193 Mrl *Process::mrl () const {
0194     if (user)
0195         return user->getMrl ();
0196     return nullptr;
0197 }
0198 
0199 static bool processRunning (QProcess *process) {
0200     return process && process->state () > QProcess::NotRunning;
0201 }
0202 
0203 bool Process::running () const {
0204     return processRunning (m_process);
0205 }
0206 
0207 void Process::setAudioLang (int) {}
0208 
0209 void Process::setSubtitle (int) {}
0210 
0211 void Process::pause () {
0212 }
0213 
0214 void Process::unpause () {
0215 }
0216 
0217 bool Process::seek (int /*pos*/, bool /*absolute*/) {
0218     return false;
0219 }
0220 
0221 void Process::volume (int /*pos*/, bool /*absolute*/) {
0222 }
0223 
0224 bool Process::saturation (int /*pos*/, bool /*absolute*/) {
0225     return false;
0226 }
0227 
0228 bool Process::hue (int /*pos*/, bool /*absolute*/) {
0229     return false;
0230 }
0231 
0232 bool Process::contrast (int /*pos*/, bool /*absolute*/) {
0233     return false;
0234 }
0235 
0236 bool Process::brightness (int /*pos*/, bool /*absolute*/) {
0237     return false;
0238 }
0239 
0240 bool Process::grabPicture (const QString &/*file*/, int /*pos*/) {
0241     m_old_state = m_state = Buffering;
0242     setState (Ready);
0243     return false;
0244 }
0245 
0246 void Process::stop () {
0247 }
0248 
0249 void Process::quit () {
0250     killProcess (m_process, view ());
0251     setState (IProcess::NotRunning);
0252 }
0253 
0254 void Process::setState (IProcess::State newstate) {
0255     if (m_state != newstate) {
0256         bool need_timer = m_old_state == m_state;
0257         m_old_state = m_state;
0258         m_state = newstate;
0259         if (need_timer)
0260             QTimer::singleShot (0, this, SLOT (rescheduledStateChanged ()));
0261     }
0262 }
0263 
0264 void Process::rescheduledStateChanged () {
0265     IProcess::State old_state = m_old_state;
0266     m_old_state = m_state;
0267     if (user) {
0268         user->stateChange (this, old_state, m_state);
0269     } else {
0270         if (m_state > IProcess::Ready)
0271             qCCritical(LOG_KMPLAYER_COMMON) << "Process running, mrl disappeared" << endl;
0272         delete this;
0273     }
0274 }
0275 
0276 bool Process::play () {
0277     Mrl *m = mrl ();
0278     if (!m)
0279         return false;
0280     bool nonstdurl = m->src.startsWith ("tv:/") ||
0281         m->src.startsWith ("dvd:") ||
0282         m->src.startsWith ("cdda:") ||
0283         m->src.startsWith ("vcd:");
0284     QString url = nonstdurl ? m->src : m->absolutePath ();
0285     bool changed = m_url != url;
0286     m_url = url;
0287     if (user) // FIXME: remove check
0288         user->starting (this);
0289     if (!changed ||
0290             KUrl (m_url).isLocalFile () ||
0291             nonstdurl ||
0292             (m_source && m_source->avoidRedirects ()))
0293         return deMediafiedPlay ();
0294     m_job = KIO::stat (m_url, KIO::HideProgressInfo);
0295     connect (m_job, SIGNAL (result (KJob *)), this, SLOT (result (KJob *)));
0296     return true;
0297 }
0298 
0299 bool Process::deMediafiedPlay () {
0300     return false;
0301 }
0302 
0303 void Process::result (KJob * job) {
0304     KIO::UDSEntry entry = static_cast <KIO::StatJob *> (job)->statResult ();
0305     QString url = entry.stringValue (KIO::UDSEntry::UDS_LOCAL_PATH);
0306     if (!url.isEmpty ())
0307         m_url = url;
0308     m_job = nullptr;
0309     deMediafiedPlay ();
0310 }
0311 
0312 void Process::terminateJobs () {
0313     if (m_job) {
0314         m_job->kill ();
0315         m_job = nullptr;
0316     }
0317 }
0318 
0319 bool Process::ready () {
0320     setState (IProcess::Ready);
0321     return true;
0322 }
0323 
0324 void Process::processStateChanged (QProcess::ProcessState nstate)
0325 {
0326     if (QProcess::Starting == m_process_state) {
0327         if (QProcess::NotRunning == nstate)
0328             setState (IProcess::NotRunning);
0329         else if (state () == IProcess::Ready)
0330             setState (IProcess::Buffering);
0331         m_process_state = nstate;
0332     }
0333 }
0334 
0335 void Process::startProcess (const QString &program, const QStringList &args)
0336 {
0337     m_process_state = QProcess::Starting;
0338     m_process->start (program, args);
0339 }
0340 
0341 View *Process::view () const {
0342     return m_source ? m_source->player ()->viewWidget () : nullptr;
0343 }
0344 
0345 //-----------------------------------------------------------------------------
0346 
0347 RecordDocument::RecordDocument (const QString &url, const QString &rurl,
0348         const QString &rec, Source *src)
0349  : SourceDocument (src, url),
0350    record_file (rurl),
0351    recorder (rec) {
0352     id = id_node_record_document;
0353 }
0354 
0355 void RecordDocument::begin () {
0356     if (!media_info) {
0357         media_info = new MediaInfo (this, MediaManager::AudioVideo);
0358         media_info->create ();
0359     }
0360     media_info->media->play ();
0361 }
0362 
0363 void RecordDocument::message (MessageType msg, void *content) {
0364     switch (msg) {
0365     case MsgMediaFinished:
0366         deactivate ();
0367         break;
0368     default:
0369         SourceDocument::message (msg, content);
0370     }
0371 }
0372 
0373 void RecordDocument::deactivate () {
0374     state = state_deactivated;
0375     ((MediaManager *) role (RoleMediaManager))->player ()->recorderStopped ();
0376     Document::deactivate ();
0377 }
0378 
0379 static RecordDocument *recordDocument (ProcessUser *user) {
0380     Mrl *mrl = user ? user->getMrl () : nullptr;
0381     return mrl && id_node_record_document == mrl->id
0382         ? static_cast <RecordDocument *> (mrl) : nullptr;
0383 }
0384 
0385 //-----------------------------------------------------------------------------
0386 
0387 static bool proxyForURL (const KUrl &url, QString &proxy) {
0388     KProtocolManager::slaveProtocol (url, proxy);
0389     return !proxy.isNull ();
0390 }
0391 
0392 //-----------------------------------------------------------------------------
0393 
0394 MPlayerBase::MPlayerBase (QObject *parent, ProcessInfo *pinfo, Settings * settings)
0395     : Process (parent, pinfo, settings),
0396       m_needs_restarted (false) {
0397     m_process = new QProcess;
0398 }
0399 
0400 MPlayerBase::~MPlayerBase () {
0401 }
0402 
0403 void MPlayerBase::initProcess () {
0404     Process::initProcess ();
0405     const KUrl &url (m_source->url ());
0406     if (!url.isEmpty ()) {
0407         QString proxy_url;
0408         if (KProtocolManager::useProxy () && proxyForURL (url, proxy_url)) {
0409             QStringList env = m_process->environment ();
0410             env << (QString ("http_proxy=") + proxy_url);
0411             m_process->setEnvironment (env);
0412         }
0413     }
0414     connect (m_process, SIGNAL (bytesWritten (qint64)),
0415             this, SLOT (dataWritten (qint64)));
0416     connect (m_process, SIGNAL (finished (int, QProcess::ExitStatus)),
0417             this, SLOT (processStopped (int, QProcess::ExitStatus)));
0418 }
0419 
0420 bool MPlayerBase::removeQueued (const char *cmd) {
0421     for (QList<QByteArray>::iterator i = commands.begin ();
0422             i != commands.end ();
0423             ++i)
0424         if (!strncmp ((*i).data (), cmd, strlen (cmd))) {
0425             commands.erase (i);
0426             return true;
0427         }
0428     return false;
0429 }
0430 
0431 bool MPlayerBase::sendCommand (const QString & cmd) {
0432     if (running ()) {
0433         commands.push_front (QString (cmd + '\n').toAscii ());
0434         fprintf (stderr, "eval %s", commands.last ().constData ());
0435         if (commands.size () < 2)
0436             m_process->write (commands.last ());
0437         return true;
0438     }
0439     return false;
0440 }
0441 
0442 void MPlayerBase::stop () {
0443     terminateJobs ();
0444 }
0445 
0446 void MPlayerBase::quit () {
0447     if (running ()) {
0448         qCDebug(LOG_KMPLAYER_COMMON) << "MPlayerBase::quit";
0449         stop ();
0450         disconnect (m_process, SIGNAL (finished (int, QProcess::ExitStatus)),
0451                 this, SLOT (processStopped (int, QProcess::ExitStatus)));
0452         m_process->waitForFinished (2000);
0453         if (running ())
0454             Process::quit ();
0455         commands.clear ();
0456         m_needs_restarted = false;
0457         processStopped ();
0458     }
0459     Process::quit ();
0460 }
0461 
0462 void MPlayerBase::dataWritten (qint64) {
0463     if (!commands.size ()) return;
0464     qCDebug(LOG_KMPLAYER_COMMON) << "eval done " << commands.last ().data ();
0465     commands.pop_back ();
0466     if (commands.size ())
0467         m_process->write (commands.last ());
0468 }
0469 
0470 void MPlayerBase::processStopped () {
0471     setState (IProcess::Ready);
0472 }
0473 
0474 void MPlayerBase::processStopped (int, QProcess::ExitStatus) {
0475     qCDebug(LOG_KMPLAYER_COMMON) << "process stopped" << endl;
0476     commands.clear ();
0477     processStopped ();
0478 }
0479 
0480 //-----------------------------------------------------------------------------
0481 
0482 static const char *mplayer_supports [] = {
0483     "dvdsource", "exitsource", "introsource", "pipesource", "tvscanner", "tvsource", "urlsource", "vcdsource", "audiocdsource", nullptr
0484 };
0485 
0486 MPlayerProcessInfo::MPlayerProcessInfo (MediaManager *mgr)
0487  : ProcessInfo ("mplayer", i18n ("&MPlayer"), mplayer_supports,
0488          mgr, new MPlayerPreferencesPage ()) {}
0489 
0490 IProcess *MPlayerProcessInfo::create (PartBase *part, ProcessUser *usr) {
0491     MPlayer *m = new MPlayer (part, this, part->settings ());
0492     m->setSource (part->source ());
0493     m->user = usr;
0494     part->processCreated (m);
0495     return m;
0496 }
0497 
0498 MPlayer::MPlayer (QObject *parent, ProcessInfo *pinfo, Settings *settings)
0499  : MPlayerBase (parent, pinfo, settings),
0500    m_widget (nullptr),
0501    m_transition_state (NotRunning),
0502    aid (-1), sid (-1)
0503 {}
0504 
0505 MPlayer::~MPlayer () {
0506     if (m_widget && !m_widget->parent ())
0507         delete m_widget;
0508 }
0509 
0510 void MPlayer::init () {
0511 }
0512 
0513 bool MPlayer::ready () {
0514     Process::ready ();
0515     if (user && user->viewer ())
0516         user->viewer ()->useIndirectWidget (true);
0517     return false;
0518 }
0519 
0520 bool MPlayer::deMediafiedPlay () {
0521     if (running ())
0522         return sendCommand (QString ("gui_play"));
0523 
0524     m_transition_state = NotRunning;
0525     if (!m_needs_restarted && running ())
0526         quit (); // rescheduling of setState will reset state just-in-time
0527 
0528     initProcess ();
0529     connect (m_process, SIGNAL (readyReadStandardOutput ()),
0530             this, SLOT (processOutput ()));
0531     connect (m_process, SIGNAL (readyReadStandardError ()),
0532             this, SLOT (processOutput ()));
0533 
0534     m_process_output = QString ();
0535     m_source->setPosition (0);
0536     if (!m_needs_restarted) {
0537         if (m_source->identified ()) {
0538             aid = m_source->audioLangId ();
0539             sid = m_source->subTitleId ();
0540         } else {
0541             aid = sid = -1;
0542         }
0543     } else {
0544         m_needs_restarted = false;
0545     }
0546     alanglist = nullptr;
0547     slanglist = nullptr;
0548     slanglist_end = nullptr;
0549     alanglist_end = nullptr;
0550     m_request_seek = -1;
0551     m_tmpURL.truncate (0);
0552 
0553     QStringList args;
0554     //m_view->consoleOutput ()->clear ();
0555     MPlayerPreferencesPage *cfg_page = static_cast <MPlayerPreferencesPage *>(process_info->config_page);
0556     QString exe = cfg_page->mplayer_path;
0557     if (exe.isEmpty ())
0558         exe = "mplayer";
0559 
0560     args << "-wid" << QString::number (widget ());
0561     args << "-slave";
0562 
0563     QString strVideoDriver = QString (m_settings->videodrivers[m_settings->videodriver].driver);
0564     if (!strVideoDriver.isEmpty ()) {
0565         args << "-vo" << strVideoDriver.toLower();
0566         if (view () && view ()->keepSizeRatio () &&
0567                 strVideoDriver.toLower() == QString::fromLatin1 ("x11"))
0568             args << "-zoom";
0569     }
0570 
0571     QString strAudioDriver = QString (m_settings->audiodrivers[m_settings->audiodriver].driver);
0572     if (!strAudioDriver.isEmpty ())
0573         args << "-ao" << strAudioDriver.toLower();
0574 
0575     if (m_settings->framedrop)
0576         args << "-framedrop";
0577 
0578     if (cfg_page->additionalarguments.length () > 0)
0579         args << KShell::splitArgs (cfg_page->additionalarguments);
0580 
0581     // postproc thingies
0582     args << KShell::splitArgs (m_source->filterOptions ());
0583 
0584     if (m_settings->autoadjustcolors) {
0585         args << "-contrast" << QString::number (m_settings->contrast);
0586         args << "-brightness" <<QString::number(m_settings->brightness);
0587         args << "-hue" << QString::number (m_settings->hue);
0588         args << "-saturation" <<QString::number(m_settings->saturation);
0589     }
0590 
0591     if (aid > -1)
0592         args << "-aid" << QString::number (aid);
0593 
0594     if (sid > -1)
0595         args << "-sid" << QString::number (sid);
0596 
0597     for (Node *n = mrl (); n; n = n->parentNode ()) {
0598         if (n->id != id_node_group_node && n->id != id_node_playlist_item)
0599             break;
0600         QString plops = static_cast<Element *>(n)->getAttribute ("mplayeropts");
0601         if (!plops.isNull ()) {
0602             QStringList sl = plops.split (QChar (' '));
0603             for (int i = 0; i < sl.size (); ++i)
0604                 args << sl[i];
0605             break;
0606         }
0607     }
0608 
0609     args << KShell::splitArgs (m_source->options ());
0610 
0611     KUrl url (m_url);
0612     if (!url.isEmpty ()) {
0613         if (m_source->url ().isLocalFile ())
0614             m_process->setWorkingDirectory
0615                 (QFileInfo (m_source->url ().path ()).absolutePath ());
0616         if (url.isLocalFile ()) {
0617             m_url = url.toLocalFile ();
0618             if (cfg_page->alwaysbuildindex &&
0619                     (m_url.toLower ().endsWith (".avi") ||
0620                      m_url.toLower ().endsWith (".divx")))
0621                 args << "-idx";
0622         } else {
0623             int cache = cfg_page->cachesize;
0624             if (cache > 3 && !url.url ().startsWith (QString ("dvd")) &&
0625                     !url.url ().startsWith (QString ("vcd")) &&
0626                     !m_url.startsWith (QString ("tv://")))
0627                 args << "-cache" << QString::number (cache);
0628             if (m_url.startsWith (QString ("cdda:/")) &&
0629                     !m_url.startsWith (QString ("cdda://")))
0630                 m_url = QString ("cdda://") + m_url.mid (6);
0631         }
0632         if (url.scheme () != QString ("stdin"))
0633             args << encodeFileOrUrl (m_url);
0634     }
0635     Mrl *m = mrl ();
0636     if (m && m->repeat > 0)
0637         args << "-loop" << QString::number (m->repeat);
0638     else if (m_settings->loop)
0639         args << "-loop" << nullptr;
0640     args << "-identify";
0641     const QString surl = encodeFileOrUrl (m_source->subUrl ());
0642     if (!surl.isEmpty ())
0643         args << "-sub" << surl;
0644     qCDebug(LOG_KMPLAYER_COMMON, "mplayer %s\n", args.join (" ").toLocal8Bit ().constData ());
0645 
0646     startProcess (exe, args);
0647 
0648     old_volume = view () ? view ()->controlPanel ()->volumeBar ()->value () : 0;
0649 
0650     return true;
0651 }
0652 
0653 void MPlayer::stop () {
0654     terminateJobs ();
0655     if (!m_source || !running ())
0656         return;
0657     sendCommand (QString ("quit"));
0658     MPlayerBase::stop ();
0659 }
0660 
0661 void MPlayer::pause () {
0662     if (Paused != m_transition_state) {
0663         m_transition_state = Paused;
0664         if (!removeQueued ("pause"))
0665             sendCommand (QString ("pause"));
0666     }
0667 }
0668 
0669 void MPlayer::unpause () {
0670     if (m_transition_state == Paused
0671             || (Paused == m_state
0672                 && m_transition_state != Playing)) {
0673         m_transition_state = Playing;
0674         if (!removeQueued ("pause"))
0675             sendCommand (QString ("pause"));
0676     }
0677 }
0678 
0679 bool MPlayer::seek (int pos, bool absolute) {
0680     if (!m_source || !m_source->hasLength () ||
0681             (absolute && m_source->position () == pos))
0682         return false;
0683     if (m_request_seek >= 0 && commands.size () > 1) {
0684         QList<QByteArray>::iterator i = commands.begin ();
0685         for (++i; i != commands.end (); ++i)
0686             if (!strncmp ((*i).data (), "seek", 4)) {
0687                 i = commands.erase (i);
0688                 m_request_seek = -1;
0689                 break;
0690             }
0691     }
0692     if (m_request_seek >= 0) {
0693         //m_request_seek = pos;
0694         return false;
0695     }
0696     m_request_seek = pos;
0697     QString cmd;
0698     cmd.sprintf ("seek %d %d", pos/10, absolute ? 2 : 0);
0699     if (!absolute)
0700         pos = m_source->position () + pos;
0701     m_source->setPosition (pos);
0702     return sendCommand (cmd);
0703 }
0704 
0705 void MPlayer::volume (int incdec, bool absolute) {
0706     if (absolute)
0707         incdec -= old_volume;
0708     if (incdec == 0)
0709         return;
0710     old_volume += incdec;
0711     sendCommand (QString ("volume ") + QString::number (incdec));
0712 }
0713 
0714 bool MPlayer::saturation (int val, bool absolute) {
0715     QString cmd;
0716     cmd.sprintf ("saturation %d %d", val, absolute ? 1 : 0);
0717     return sendCommand (cmd);
0718 }
0719 
0720 bool MPlayer::hue (int val, bool absolute) {
0721     QString cmd;
0722     cmd.sprintf ("hue %d %d", val, absolute ? 1 : 0);
0723     return sendCommand (cmd);
0724 }
0725 
0726 bool MPlayer::contrast (int val, bool /*absolute*/) {
0727     QString cmd;
0728     cmd.sprintf ("contrast %d 1", val);
0729     return sendCommand (cmd);
0730 }
0731 
0732 bool MPlayer::brightness (int val, bool /*absolute*/) {
0733     QString cmd;
0734     cmd.sprintf ("brightness %d 1", val);
0735     return sendCommand (cmd);
0736 }
0737 
0738 bool MPlayer::grabPicture (const QString &file, int pos) {
0739     Mrl *m = mrl ();
0740     if (m_state > Ready || !m || m->src.isEmpty ())
0741         return false; //FIXME
0742     initProcess ();
0743     m_old_state = m_state = Buffering;
0744     unlink (file.toAscii ().constData ());
0745     QByteArray ba = file.toLocal8Bit ();
0746     ba.append ("XXXXXX");
0747     if (mkdtemp ((char *) ba.constData ())) {
0748         m_grab_dir = QString::fromLocal8Bit (ba.constData ());
0749         QString exe ("mplayer");
0750         QStringList args;
0751         QString jpgopts ("jpeg:outdir=");
0752         jpgopts += KShell::quoteArg (m_grab_dir);
0753         args << "-vo" << jpgopts;
0754         args << "-frames" << "1" << "-nosound" << "-quiet";
0755         if (pos > 0)
0756             args << "-ss" << QString::number (pos);
0757         args << encodeFileOrUrl (m->src);
0758         qCDebug(LOG_KMPLAYER_COMMON) << args.join (" ");
0759         m_process->start (exe, args);
0760         if (m_process->waitForStarted ()) {
0761             m_grab_file = file;
0762             setState (Playing);
0763             return true;
0764         } else {
0765             rmdir (ba.constData ());
0766             m_grab_dir.truncate (0);
0767         }
0768     } else {
0769         qCCritical(LOG_KMPLAYER_COMMON) << "mkdtemp failure";
0770     }
0771     setState (Ready);
0772     return false;
0773 }
0774 
0775 void MPlayer::processOutput () {
0776     const QByteArray ba = m_process->readAllStandardOutput ();
0777     const char *str = ba.constData ();
0778     int slen = ba.size ();
0779     if (!mrl () || slen <= 0) return;
0780     View *v = view ();
0781 
0782     bool ok;
0783     QRegExp *patterns = static_cast<MPlayerPreferencesPage *>(process_info->config_page)->m_patterns;
0784     QRegExp & m_refURLRegExp = patterns[MPlayerPreferencesPage::pat_refurl];
0785     QRegExp & m_refRegExp = patterns[MPlayerPreferencesPage::pat_ref];
0786     do {
0787         int len = strcspn (str, "\r\n");
0788         QString out = m_process_output + QString::fromLocal8Bit (str, len);
0789         m_process_output = QString ();
0790         str += len;
0791         slen -= len;
0792         if (slen <= 0) {
0793             m_process_output = out;
0794             break;
0795         }
0796         bool process_stats = false;
0797         if (str[0] == '\r') {
0798             if (slen > 1 && str[1] == '\n') {
0799                 str++;
0800                 slen--;
0801             } else
0802                 process_stats = true;
0803         }
0804         str++;
0805         slen--;
0806 
0807         if (process_stats) {
0808             QRegExp & m_posRegExp = patterns[MPlayerPreferencesPage::pat_pos];
0809             QRegExp & m_cacheRegExp = patterns[MPlayerPreferencesPage::pat_cache];
0810             if (m_posRegExp.indexIn (out) > -1) {
0811                 if (m_source->hasLength ()) {
0812                     int pos = int (10.0 * m_posRegExp.cap (1).toFloat ());
0813                     m_source->setPosition (pos);
0814                     m_request_seek = -1;
0815                 }
0816                 if (Playing == m_transition_state) {
0817                     m_transition_state = NotRunning;
0818                     setState (Playing);
0819                 }
0820             } else if (m_cacheRegExp.indexIn (out) > -1) {
0821                 m_source->setLoading (int (m_cacheRegExp.cap(1).toDouble()));
0822             }
0823         } else if (out.startsWith ("ID_LENGTH")) {
0824             int pos = out.indexOf ('=');
0825             if (pos > 0) {
0826                 int l = (int) out.mid (pos + 1).toDouble (&ok);
0827                 if (ok && l >= 0) {
0828                     m_source->setLength (mrl (), 10 * l);
0829                 }
0830             }
0831         } else if (out.startsWith ("ID_PAUSED")) {
0832             if (Paused == m_transition_state) {
0833                 m_transition_state = NotRunning;
0834                 setState (Paused);
0835             }
0836         } else if (m_refURLRegExp.indexIn(out) > -1) {
0837             qCDebug(LOG_KMPLAYER_COMMON) << "Reference mrl " << m_refURLRegExp.cap (1);
0838             if (!m_tmpURL.isEmpty () &&
0839                     (m_url.endsWith (m_tmpURL) || m_tmpURL.endsWith (m_url)))
0840                 m_source->insertURL (mrl (), m_tmpURL);;
0841             KUrl tmp (m_refURLRegExp.cap (1));
0842             m_tmpURL = tmp.isLocalFile () ? tmp.toLocalFile () : tmp.url ();
0843             if (m_source->url () == m_tmpURL ||
0844                     m_url.endsWith (m_tmpURL) || m_tmpURL.endsWith (m_url))
0845                 m_tmpURL.truncate (0);
0846         } else if (m_refRegExp.indexIn (out) > -1) {
0847             qCDebug(LOG_KMPLAYER_COMMON) << "Reference File ";
0848             m_tmpURL.truncate (0);
0849         } else if (out.startsWith ("ID_VIDEO_WIDTH")) {
0850             int pos = out.indexOf ('=');
0851             if (pos > 0) {
0852                 int w = out.mid (pos + 1).toInt ();
0853                 m_source->setDimensions (mrl (), w, m_source->height ());
0854             }
0855         } else if (out.startsWith ("ID_VIDEO_HEIGHT")) {
0856             int pos = out.indexOf ('=');
0857             if (pos > 0) {
0858                 int h = out.mid (pos + 1).toInt ();
0859                 m_source->setDimensions (mrl (), m_source->width (), h);
0860             }
0861         } else if (out.startsWith ("ID_VIDEO_ASPECT")) {
0862             int pos = out.indexOf ('=');
0863             if (pos > 0) {
0864                 bool ok;
0865                 QString val = out.mid (pos + 1);
0866                 float a = val.toFloat (&ok);
0867                 if (!ok) {
0868                     val.replace (',', '.');
0869                     a = val.toFloat (&ok);
0870                 }
0871                 if (ok && a > 0.001)
0872                     m_source->setAspect (mrl (), a);
0873             }
0874         } else if (out.startsWith ("ID_AID_")) {
0875             int pos = out.indexOf ('_', 7);
0876             if (pos > 0) {
0877                 int id = out.mid (7, pos - 7).toInt ();
0878                 pos = out.indexOf ('=', pos);
0879                 if (pos > 0) {
0880                     if (!alanglist_end) {
0881                         alanglist = new Source::LangInfo (id, out.mid (pos + 1));
0882                         alanglist_end = alanglist;
0883                     } else {
0884                         alanglist_end->next = new Source::LangInfo (id, out.mid(pos+1));
0885                         alanglist_end = alanglist_end->next;
0886                     }
0887                     qCDebug(LOG_KMPLAYER_COMMON) << "lang " << id << " " << alanglist_end->name;
0888                 }
0889             }
0890         } else if (out.startsWith ("ID_SID_")) {
0891             int pos = out.indexOf ('_', 7);
0892             if (pos > 0) {
0893                 int id = out.mid (7, pos - 7).toInt ();
0894                 pos = out.indexOf ('=', pos);
0895                 if (pos > 0) {
0896                     if (!slanglist_end) {
0897                         slanglist = new Source::LangInfo (id, out.mid (pos + 1));
0898                         slanglist_end = slanglist;
0899                     } else {
0900                         slanglist_end->next = new Source::LangInfo (id, out.mid(pos+1));
0901                         slanglist_end = slanglist_end->next;
0902                     }
0903                     qCDebug(LOG_KMPLAYER_COMMON) << "sid " << id << " " << slanglist_end->name;
0904                 }
0905             }
0906         } else if (out.startsWith ("ICY Info")) {
0907             int p = out.indexOf ("StreamTitle=", 8);
0908             if (p > -1) {
0909                 p += 12;
0910                 int e = out.indexOf (';', p);
0911                 if (e > -1)
0912                     e -= p;
0913                 QString inf = out.mid (p, e);
0914                 mrl ()->document ()->message (MsgInfoString, &inf);
0915             }
0916         } else if (v) {
0917             QRegExp & m_startRegExp = patterns[MPlayerPreferencesPage::pat_start];
0918             QRegExp & m_sizeRegExp = patterns[MPlayerPreferencesPage::pat_size];
0919             v->addText (out, true);
0920             if (!m_source->processOutput (out)) {
0921                 // int movie_width = m_source->width ();
0922                 if (/*movie_width <= 0 &&*/ m_sizeRegExp.indexIn (out) > -1) {
0923                     int movie_width = m_sizeRegExp.cap (1).toInt (&ok);
0924                     int movie_height = ok ? m_sizeRegExp.cap (2).toInt (&ok) : 0;
0925                     if (ok && movie_width > 0 && movie_height > 0) {
0926                         m_source->setDimensions(mrl(),movie_width,movie_height);
0927                         m_source->setAspect (mrl(), 1.0*movie_width/movie_height);
0928                     }
0929                 } else if (m_startRegExp.indexIn (out) > -1) {
0930                     if (!m_tmpURL.isEmpty () && m_url != m_tmpURL) {
0931                         m_source->insertURL (mrl (), m_tmpURL);;
0932                         m_tmpURL.truncate (0);
0933                     }
0934                     m_source->setIdentified ();
0935                     m_source->setLanguages (alanglist, slanglist);
0936                     m_source->setLoading (100);
0937                     setState (IProcess::Playing);
0938                     m_source->setPosition (0);
0939                 }
0940             }
0941         }
0942     } while (slen > 0);
0943 }
0944 
0945 void MPlayer::processStopped () {
0946     if (mrl ()) {
0947         QString url;
0948         if (!m_grab_dir.isEmpty ()) {
0949             QDir dir (m_grab_dir);
0950             QStringList files = dir.entryList ();
0951             bool renamed = false;
0952             for (int i = 0; i < files.size (); ++i) {
0953                 qCDebug(LOG_KMPLAYER_COMMON) << files[i];
0954                 if (files[i] == "." || files[i] == "..")
0955                     continue;
0956                 if (!renamed) {
0957                     qCDebug(LOG_KMPLAYER_COMMON) << "rename " << dir.filePath (files[i]) << "->" << m_grab_file;
0958                     renamed = true;
0959                     ::rename (dir.filePath (files[i]).toLocal8Bit().constData(),
0960                             m_grab_file.toLocal8Bit ().constData ());
0961                 } else {
0962                     qCDebug(LOG_KMPLAYER_COMMON) << "rm " << files[i];
0963                     dir.remove (files[i]);
0964                 }
0965             }
0966             QString dirname = dir.dirName ();
0967             dir.cdUp ();
0968             qCDebug(LOG_KMPLAYER_COMMON) << m_grab_dir << " " << files.size () << " rmdir " << dirname;
0969             dir.rmdir (dirname);
0970         }
0971         if (m_source && m_needs_restarted) {
0972             commands.clear ();
0973             int pos = m_source->position ();
0974             play ();
0975             seek (pos, true);
0976             return;
0977         }
0978     }
0979     setState (IProcess::Ready);
0980 }
0981 
0982 void MPlayer::setAudioLang (int id) {
0983     aid = id;
0984     m_needs_restarted = true;
0985     sendCommand (QString ("quit"));
0986 }
0987 
0988 void MPlayer::setSubtitle (int id) {
0989     sid = id;
0990     m_needs_restarted = true;
0991     sendCommand (QString ("quit"));
0992 }
0993 
0994 //-----------------------------------------------------------------------------
0995 
0996 extern const char * strMPlayerGroup;
0997 static const char * strMPlayerPatternGroup = "MPlayer Output Matching";
0998 static const char * strMPlayerPath = "MPlayer Path";
0999 static const char * strAddArgs = "Additional Arguments";
1000 static const char * strCacheSize = "Cache Size for Streaming";
1001 static const char * strAlwaysBuildIndex = "Always build index";
1002 static const int non_patterns = 4;
1003 
1004 static struct MPlayerPattern {
1005     KLocalizedString caption;
1006     const char * name;
1007     const char * pattern;
1008 } _mplayer_patterns [] = {
1009     { ki18n ("Size pattern"), "Movie Size", "VO:.*[^0-9]([0-9]+)x([0-9]+)" },
1010     { ki18n ("Cache pattern"), "Cache Fill", "Cache fill:[^0-9]*([0-9\\.]+)%" },
1011     { ki18n ("Position pattern"), "Movie Position", "[AV]:\\s*([0-9\\.]+)" },
1012     { ki18n ("Index pattern"), "Index Pattern", "Generating Index: +([0-9]+)%" },
1013     { ki18n ("Reference URL pattern"), "Reference URL Pattern", "Playing\\s+(.*[^\\.])\\.?\\s*$" },
1014     { ki18n ("Reference pattern"), "Reference Pattern", "Reference Media file" },
1015     { ki18n ("Start pattern"), "Start Playing", "Start[^ ]* play" },
1016     { ki18n ("VCD track pattern"), "VCD Tracks", "track ([0-9]+):" },
1017     { ki18n ("Audio CD tracks pattern"), "CDROM Tracks", "[Aa]udio CD[^0-9]+([0-9]+)[^0-9]tracks" }
1018 };
1019 
1020 namespace KMPlayer {
1021 
1022 class MPlayerPreferencesFrame : public QFrame
1023 {
1024 public:
1025     MPlayerPreferencesFrame (QWidget * parent);
1026     QTableWidget * table;
1027 };
1028 
1029 } // namespace
1030 
1031 MPlayerPreferencesFrame::MPlayerPreferencesFrame (QWidget * parent)
1032  : QFrame (parent) {
1033     QVBoxLayout * layout = new QVBoxLayout (this);
1034     table = new QTableWidget (int (MPlayerPreferencesPage::pat_last)+non_patterns, 2, this);
1035     table->verticalHeader ()->setVisible (false);
1036     table->horizontalHeader ()->setVisible (false);
1037     table->setContentsMargins (0, 0, 0, 0);
1038     table->setItem (0, 0, new QTableWidgetItem (i18n ("MPlayer command:")));
1039     table->setItem (0, 1, new QTableWidgetItem ());
1040     table->setItem (1, 0, new QTableWidgetItem (i18n ("Additional command line arguments:")));
1041     table->setItem (1, 1, new QTableWidgetItem ());
1042     table->setItem (2, 0, new QTableWidgetItem (QString("%1 (%2)").arg (i18n ("Cache size:")).arg (i18n ("kB")))); // FIXME for new translations
1043     QSpinBox* spin = new QSpinBox(table->viewport());
1044     spin->setMaximum(32767);
1045     spin->setSingleStep(32);
1046     table->setCellWidget (2, 1, spin);
1047     table->setItem (3, 0, new QTableWidgetItem (i18n ("Build new index when possible")));
1048     table->setCellWidget (3, 1, new QCheckBox (table->viewport()));
1049     table->cellWidget (3, 1)->setWhatsThis(i18n ("Allows seeking in indexed files (AVIs)"));
1050     for (int i = 0; i < int (MPlayerPreferencesPage::pat_last); i++) {
1051         table->setItem (i+non_patterns, 0, new QTableWidgetItem (_mplayer_patterns[i].caption.toString()));
1052         table->setItem (i+non_patterns, 1, new QTableWidgetItem ());
1053     }
1054     for (int i = 0; i < non_patterns + int (MPlayerPreferencesPage::pat_last); i++) {
1055         QTableWidgetItem *item = table->itemAt (i, 0);
1056         item->setFlags (item->flags () ^ Qt::ItemIsEditable);
1057     }
1058     table->horizontalHeader ()->setResizeMode (QHeaderView::ResizeToContents);
1059     table->horizontalHeader ()->setStretchLastSection (true);
1060     table->resizeRowsToContents ();
1061     layout->addWidget (table);
1062 }
1063 
1064 MPlayerPreferencesPage::MPlayerPreferencesPage ()
1065  : m_configframe (nullptr) {
1066 }
1067 
1068 void MPlayerPreferencesPage::write (KSharedConfigPtr config) {
1069     KConfigGroup patterns_cfg (config, strMPlayerPatternGroup);
1070     for (int i = 0; i < int (pat_last); i++)
1071         patterns_cfg.writeEntry
1072             (_mplayer_patterns[i].name, m_patterns[i].pattern ());
1073     KConfigGroup mplayer_cfg (config, strMPlayerGroup);
1074     mplayer_cfg.writeEntry (strMPlayerPath, mplayer_path);
1075     mplayer_cfg.writeEntry (strAddArgs, additionalarguments);
1076     mplayer_cfg.writeEntry (strCacheSize, cachesize);
1077     mplayer_cfg.writeEntry (strAlwaysBuildIndex, alwaysbuildindex);
1078 }
1079 
1080 void MPlayerPreferencesPage::read (KSharedConfigPtr config) {
1081     KConfigGroup patterns_cfg (config, strMPlayerPatternGroup);
1082     for (int i = 0; i < int (pat_last); i++)
1083         m_patterns[i].setPattern (patterns_cfg.readEntry
1084                 (_mplayer_patterns[i].name, _mplayer_patterns[i].pattern));
1085     KConfigGroup mplayer_cfg (config, strMPlayerGroup);
1086     mplayer_path = mplayer_cfg.readEntry (strMPlayerPath, QString ("mplayer"));
1087     additionalarguments = mplayer_cfg.readEntry (strAddArgs, QString ());
1088     cachesize = mplayer_cfg.readEntry (strCacheSize, 384);
1089     alwaysbuildindex = mplayer_cfg.readEntry (strAlwaysBuildIndex, false);
1090 }
1091 
1092 void MPlayerPreferencesPage::sync (bool fromUI) {
1093     QTableWidget * table = m_configframe->table;
1094     QSpinBox * cacheSize = static_cast<QSpinBox *>(table->cellWidget (2, 1));
1095     QCheckBox * buildIndex = static_cast<QCheckBox *>(table->cellWidget (3, 1));
1096     if (fromUI) {
1097         mplayer_path = table->item (0, 1)->text ();
1098         additionalarguments = table->item (1, 1)->text ();
1099         for (int i = 0; i < int (pat_last); i++)
1100             m_patterns[i].setPattern (table->item (i+non_patterns, 1)->text ());
1101         cachesize = cacheSize->value();
1102         alwaysbuildindex = buildIndex->isChecked ();
1103     } else {
1104         table->item (0, 1)->setText (mplayer_path);
1105         table->item (1, 1)->setText (additionalarguments);
1106         for (int i = 0; i < int (pat_last); i++)
1107             table->item (i+non_patterns, 1)->setText (m_patterns[i].pattern ());
1108         if (cachesize > 0)
1109             cacheSize->setValue(cachesize);
1110         buildIndex->setChecked (alwaysbuildindex);
1111     }
1112 }
1113 
1114 void MPlayerPreferencesPage::prefLocation (QString & item, QString & icon, QString & tab) {
1115     item = i18n ("General Options");
1116     icon = QString ("kmplayer");
1117     tab = i18n ("MPlayer");
1118 }
1119 
1120 QFrame * MPlayerPreferencesPage::prefPage (QWidget * parent) {
1121     m_configframe = new MPlayerPreferencesFrame (parent);
1122     return m_configframe;
1123 }
1124 
1125 //-----------------------------------------------------------------------------
1126 
1127 static const char * mencoder_supports [] = {
1128     "dvdsource", "pipesource", "tvscanner", "tvsource", "urlsource",
1129     "vcdsource", "audiocdsource", nullptr
1130 };
1131 
1132 MEncoderProcessInfo::MEncoderProcessInfo (MediaManager *mgr)
1133  : ProcessInfo ("mencoder", i18n ("M&Encoder"), mencoder_supports,
1134          mgr, nullptr) {}
1135 
1136 IProcess *MEncoderProcessInfo::create (PartBase *part, ProcessUser *usr) {
1137     MEncoder *m = new MEncoder (part, this, part->settings ());
1138     m->setSource (part->source ());
1139     m->user = usr;
1140     part->processCreated (m);
1141     return m;
1142 }
1143 
1144 MEncoder::MEncoder (QObject * parent, ProcessInfo *pinfo, Settings * settings)
1145  : MPlayerBase (parent, pinfo, settings) {}
1146 
1147 MEncoder::~MEncoder () {
1148 }
1149 
1150 void MEncoder::init () {
1151 }
1152 
1153 bool MEncoder::deMediafiedPlay () {
1154     stop ();
1155     RecordDocument *rd = recordDocument (user);
1156     if (!rd)
1157         return false;
1158     initProcess ();
1159     QString exe ("mencoder");
1160     QString margs = m_settings->mencoderarguments;
1161     if (m_settings->recordcopy)
1162         margs = QString ("-oac copy -ovc copy");
1163     QStringList args = KShell::splitArgs (margs);
1164     if (m_source)
1165         args << KShell::splitArgs (m_source->recordCmd ());
1166     // FIXME if (m_player->source () == source) // ugly
1167     //    m_player->stop ();
1168     QString myurl = encodeFileOrUrl (m_url);
1169     if (!myurl.isEmpty ())
1170         args << myurl;
1171     args << "-o" << encodeFileOrUrl (rd->record_file);
1172     startProcess (exe, args);
1173     qCDebug(LOG_KMPLAYER_COMMON, "mencoder %s\n", args.join (" ").toLocal8Bit ().constData ());
1174     if (m_process->waitForStarted ()) {
1175         setState (Playing);
1176         return true;
1177     }
1178     stop ();
1179     return false;
1180 }
1181 
1182 void MEncoder::stop () {
1183     terminateJobs ();
1184     if (running ()) {
1185         qCDebug(LOG_KMPLAYER_COMMON) << "MEncoder::stop ()";
1186         Process::quit ();
1187         MPlayerBase::stop ();
1188     }
1189 }
1190 
1191 //-----------------------------------------------------------------------------
1192 
1193 static const char * mplayerdump_supports [] = {
1194     "dvdsource", "pipesource", "tvscanner", "tvsource", "urlsource", "vcdsource", "audiocdsource", nullptr
1195 };
1196 
1197 MPlayerDumpProcessInfo::MPlayerDumpProcessInfo (MediaManager *mgr)
1198  : ProcessInfo ("mplayerdumpstream", i18n ("&MPlayerDumpstream"),
1199          mplayerdump_supports, mgr, nullptr) {}
1200 
1201 IProcess *MPlayerDumpProcessInfo::create (PartBase *p, ProcessUser *usr) {
1202     MPlayerDumpstream *m = new MPlayerDumpstream (p, this, p->settings ());
1203     m->setSource (p->source ());
1204     m->user = usr;
1205     p->processCreated (m);
1206     return m;
1207 }
1208 
1209 MPlayerDumpstream::MPlayerDumpstream (QObject *p, ProcessInfo *pi, Settings *s)
1210  : MPlayerBase (p, pi, s) {}
1211 
1212 MPlayerDumpstream::~MPlayerDumpstream () {
1213 }
1214 
1215 void MPlayerDumpstream::init () {
1216 }
1217 
1218 bool MPlayerDumpstream::deMediafiedPlay () {
1219     stop ();
1220     RecordDocument *rd = recordDocument (user);
1221     if (!rd)
1222         return false;
1223     initProcess ();
1224     QString exe ("mplayer");
1225     QStringList args;
1226     args << KShell::splitArgs (m_source->recordCmd ());
1227     // FIXME if (m_player->source () == source) // ugly
1228     //    m_player->stop ();
1229     QString myurl = encodeFileOrUrl (m_url);
1230     if (!myurl.isEmpty ())
1231         args << myurl;
1232     args << "-dumpstream" << "-dumpfile" << encodeFileOrUrl (rd->record_file);
1233     qCDebug(LOG_KMPLAYER_COMMON, "mplayer %s\n", args.join (" ").toLocal8Bit ().constData ());
1234     startProcess (exe, args);
1235     if (m_process->waitForStarted ()) {
1236         setState (Playing);
1237         return true;
1238     }
1239     stop ();
1240     return false;
1241 }
1242 
1243 void MPlayerDumpstream::stop () {
1244     terminateJobs ();
1245     if (!m_source || !running ())
1246         return;
1247     qCDebug(LOG_KMPLAYER_COMMON) << "MPlayerDumpstream::stop";
1248     if (running ())
1249         Process::quit ();
1250     MPlayerBase::stop ();
1251 }
1252 
1253 //-----------------------------------------------------------------------------
1254 
1255 MasterProcessInfo::MasterProcessInfo (const char *nm, const QString &lbl,
1256             const char **supported, MediaManager *mgr, PreferencesPage *pp)
1257  : ProcessInfo (nm, lbl, supported, mgr, pp),
1258    m_slave (nullptr) {}
1259 
1260 MasterProcessInfo::~MasterProcessInfo () {
1261     stopSlave ();
1262 }
1263 
1264 void MasterProcessInfo::initSlave () {
1265     if (m_path.isEmpty ()) {
1266         static int count = 0;
1267         m_path = QString ("/master_%1").arg (count++);
1268         (void) new MasterAdaptor (this);
1269         QDBusConnection::sessionBus().registerObject (m_path, this);
1270         m_service = QDBusConnection::sessionBus().baseService ();
1271     }
1272     setupProcess (&m_slave);
1273     connect (m_slave, SIGNAL (finished (int, QProcess::ExitStatus)),
1274             this, SLOT (slaveStopped (int, QProcess::ExitStatus)));
1275     connect (m_slave, SIGNAL (readyReadStandardOutput ()),
1276             this, SLOT (slaveOutput ()));
1277     connect (m_slave, SIGNAL (readyReadStandardError ()),
1278             this, SLOT (slaveOutput ()));
1279 }
1280 
1281 void MasterProcessInfo::quitProcesses () {
1282     stopSlave ();
1283 }
1284 
1285 void MasterProcessInfo::stopSlave () {
1286     if (!m_slave_service.isEmpty ()) {
1287         QDBusMessage msg = QDBusMessage::createMethodCall (
1288                 m_slave_service, QString ("/%1").arg (ProcessInfo::name),
1289                 "org.kde.kmplayer.Slave", "quit");
1290         //msg << m_url << mime << plugin << param_len;
1291         msg.setDelayedReply (false);
1292         QDBusConnection::sessionBus().send (msg);
1293     }
1294     if (processRunning (m_slave)) {
1295         m_slave->waitForFinished (1000);
1296         killProcess (m_slave, manager->player ()->view ());
1297     }
1298 }
1299 
1300 void MasterProcessInfo::running (const QString &srv) {
1301     qCDebug(LOG_KMPLAYER_COMMON) << "MasterProcessInfo::running " << srv;
1302     m_slave_service = srv;
1303     MediaManager::ProcessList &pl = manager->processes ();
1304     const MediaManager::ProcessList::iterator e = pl.end ();
1305     for (MediaManager::ProcessList::iterator i = pl.begin (); i != e; ++i)
1306         if (this == (*i)->process_info)
1307             static_cast <Process *> (*i)->setState (IProcess::Ready);
1308 }
1309 
1310 void MasterProcessInfo::slaveStopped (int, QProcess::ExitStatus) {
1311     m_slave_service.truncate (0);
1312     MediaManager::ProcessList &pl = manager->processes ();
1313     const MediaManager::ProcessList::iterator e = pl.end ();
1314     for (MediaManager::ProcessList::iterator i = pl.begin (); i != e; ++i)
1315         if (this == (*i)->process_info)
1316             static_cast <Process *> (*i)->setState (IProcess::NotRunning);
1317 }
1318 
1319 void MasterProcessInfo::slaveOutput () {
1320     outputToView(manager->player()->viewWidget(), m_slave->readAllStandardOutput());
1321     outputToView(manager->player()->viewWidget(), m_slave->readAllStandardError ());
1322 }
1323 
1324 MasterProcess::MasterProcess (QObject *parent, ProcessInfo *pinfo, Settings *settings)
1325  : Process (parent, pinfo, settings) {}
1326 
1327 MasterProcess::~MasterProcess () {
1328 }
1329 
1330 void MasterProcess::init () {
1331 }
1332 
1333 bool MasterProcess::deMediafiedPlay () {
1334     WindowId wid = user->viewer ()->windowHandle ();
1335     m_slave_path = QString ("/stream_%1").arg (wid);
1336     MasterProcessInfo *mpi = static_cast <MasterProcessInfo *>(process_info);
1337     qCDebug(LOG_KMPLAYER_COMMON) << "MasterProcess::deMediafiedPlay " << m_url << " " << wid;
1338 
1339     (void) new StreamMasterAdaptor (this);
1340     QDBusConnection::sessionBus().registerObject (
1341             QString ("%1/stream_%2").arg (mpi->m_path).arg (wid), this);
1342 
1343     QDBusMessage msg = QDBusMessage::createMethodCall (
1344             mpi->m_slave_service, QString ("/%1").arg (process_info->name),
1345                 "org.kde.kmplayer.Slave", "newStream");
1346     if (!m_url.startsWith ("dvd:") ||
1347             !m_url.startsWith ("vcd:") ||
1348             !m_url.startsWith ("cdda:")) {
1349         KUrl url (m_url);
1350         if (url.isLocalFile ())
1351             m_url = getPath (url);
1352     }
1353     msg << m_url << (qulonglong)wid;
1354     msg.setDelayedReply (false);
1355     QDBusConnection::sessionBus().send (msg);
1356     setState (IProcess::Buffering);
1357     return true;
1358 }
1359 
1360 bool MasterProcess::running () const {
1361     MasterProcessInfo *mpi = static_cast <MasterProcessInfo *>(process_info);
1362     return processRunning (mpi->m_slave);
1363 }
1364 
1365 void MasterProcess::loading (int perc) {
1366     process_info->manager->player ()->setLoaded (perc);
1367 }
1368 
1369 void MasterProcess::streamInfo (uint64_t length, double aspect) {
1370     qCDebug(LOG_KMPLAYER_COMMON) << length;
1371     m_source->setLength (mrl (), length);
1372     m_source->setAspect (mrl (), aspect);
1373 }
1374 
1375 void MasterProcess::streamMetaInfo (QString info) {
1376     m_source->document ()->message (MsgInfoString, &info);
1377 }
1378 
1379 void MasterProcess::playing () {
1380     process_info->manager->player ()->setLoaded (100);
1381     setState (IProcess::Playing);
1382 }
1383 
1384 void MasterProcess::progress (uint64_t pos) {
1385     m_source->setPosition (pos);
1386 }
1387 
1388 void MasterProcess::pause () {
1389     if (IProcess::Playing == m_state) {
1390         MasterProcessInfo *mpi = static_cast<MasterProcessInfo *>(process_info);
1391         QDBusMessage msg = QDBusMessage::createMethodCall (
1392                 mpi->m_slave_service,
1393                 m_slave_path,
1394                 "org.kde.kmplayer.StreamSlave",
1395                 "pause");
1396         msg.setDelayedReply (false);
1397         QDBusConnection::sessionBus().send (msg);
1398     }
1399 }
1400 
1401 void MasterProcess::unpause () {
1402     pause ();
1403 }
1404 
1405 bool MasterProcess::seek (int pos, bool) {
1406     if (IProcess::Playing == m_state) {
1407         MasterProcessInfo *mpi = static_cast<MasterProcessInfo *>(process_info);
1408         QDBusMessage msg = QDBusMessage::createMethodCall (
1409                 mpi->m_slave_service,
1410                 m_slave_path,
1411                 "org.kde.kmplayer.StreamSlave",
1412                 "seek");
1413         msg << (qulonglong) pos << true;
1414         msg.setDelayedReply (false);
1415         QDBusConnection::sessionBus().send (msg);
1416         return true;
1417     }
1418     return false;
1419 }
1420 
1421 void MasterProcess::volume (int incdec, bool) {
1422     if (IProcess::Playing == m_state) {
1423         MasterProcessInfo *mpi = static_cast<MasterProcessInfo *>(process_info);
1424         QDBusMessage msg = QDBusMessage::createMethodCall (
1425                 mpi->m_slave_service,
1426                 m_slave_path,
1427                 "org.kde.kmplayer.StreamSlave",
1428                 "volume");
1429         msg << incdec;
1430         msg.setDelayedReply (false);
1431         QDBusConnection::sessionBus().send (msg);
1432     }
1433 }
1434 
1435 void MasterProcess::eof () {
1436     setState (IProcess::Ready);
1437 }
1438 
1439 void MasterProcess::stop () {
1440     if (m_state > IProcess::Ready) {
1441         MasterProcessInfo *mpi = static_cast<MasterProcessInfo *>(process_info);
1442         QDBusMessage msg = QDBusMessage::createMethodCall (
1443                 mpi->m_slave_service,
1444                 m_slave_path,
1445                 "org.kde.kmplayer.StreamSlave",
1446                 "stop");
1447         msg.setDelayedReply (false);
1448         QDBusConnection::sessionBus().send (msg);
1449     }
1450 }
1451 
1452 //-------------------------%<--------------------------------------------------
1453 
1454 static const char *phonon_supports [] = {
1455     "urlsource", "dvdsource", "vcdsource", "audiocdsource", nullptr
1456 };
1457 
1458 PhononProcessInfo::PhononProcessInfo (MediaManager *mgr)
1459   : MasterProcessInfo ("phonon", i18n ("&Phonon"), phonon_supports, mgr, nullptr)
1460 {}
1461 
1462 IProcess *PhononProcessInfo::create (PartBase *part, ProcessUser *usr) {
1463     if (!processRunning (m_slave))
1464         startSlave ();
1465     Phonon *p = new Phonon (part, this, part->settings ());
1466     p->setSource (part->source ());
1467     p->user = usr;
1468     part->processCreated (p);
1469     return p;
1470 }
1471 
1472 bool PhononProcessInfo::startSlave () {
1473     initSlave ();
1474     QString exe ("kphononplayer");
1475     QStringList args;
1476     args << "-cb" << (m_service + m_path);
1477     qCDebug(LOG_KMPLAYER_COMMON, "kphononplayer %s", args.join (" ").toLocal8Bit ().constData ());
1478     m_slave->start (exe, args);
1479     return true;
1480 }
1481 
1482 Phonon::Phonon (QObject *parent, ProcessInfo *pinfo, Settings *settings)
1483  : MasterProcess (parent, pinfo, settings) {}
1484 
1485 bool Phonon::ready () {
1486     if (user && user->viewer ())
1487         user->viewer ()->useIndirectWidget (false);
1488     qCDebug(LOG_KMPLAYER_COMMON) << "Phonon::ready " << state () << endl;
1489     PhononProcessInfo *ppi = static_cast <PhononProcessInfo *>(process_info);
1490     if (running ()) {
1491         if (!ppi->m_slave_service.isEmpty ())
1492             setState (IProcess::Ready);
1493         return true;
1494     } else {
1495         return ppi->startSlave ();
1496     }
1497 }
1498 
1499 //-----------------------------------------------------------------------------
1500 
1501 ConfigDocument::ConfigDocument ()
1502     : Document (QString ()) {}
1503 
1504 ConfigDocument::~ConfigDocument () {
1505     qCDebug(LOG_KMPLAYER_COMMON) << "~ConfigDocument";
1506 }
1507 
1508 namespace KMPlayer {
1509     /*
1510      * Element for ConfigDocument
1511      */
1512     struct SomeNode : public ConfigNode {
1513         SomeNode (NodePtr & d, const QString & t)
1514             : ConfigNode (d, t) {}
1515         ~SomeNode () override {}
1516         Node *childFromTag (const QString & t) override;
1517     };
1518 } // namespace
1519 
1520 ConfigNode::ConfigNode (NodePtr & d, const QString & t)
1521     : DarkNode (d, t.toUtf8 ()), w (nullptr) {}
1522 
1523 Node *ConfigDocument::childFromTag (const QString & tag) {
1524     if (tag.toLower () == QString ("document"))
1525         return new ConfigNode (m_doc, tag);
1526     return nullptr;
1527 }
1528 
1529 Node *ConfigNode::childFromTag (const QString & t) {
1530     return new TypeNode (m_doc, t);
1531 }
1532 
1533 TypeNode::TypeNode (NodePtr & d, const QString & t)
1534  : ConfigNode (d, t), tag (t) {}
1535 
1536 Node *TypeNode::childFromTag (const QString & tag) {
1537     return new SomeNode (m_doc, tag);
1538 }
1539 
1540 Node *SomeNode::childFromTag (const QString & t) {
1541     return new SomeNode (m_doc, t);
1542 }
1543 
1544 QWidget * TypeNode::createWidget (QWidget * parent) {
1545     QByteArray ba = getAttribute (Ids::attr_type).toAscii ();
1546     const char *ctype = ba.constData ();
1547     QString value = getAttribute (Ids::attr_value);
1548     if (!strcmp (ctype, "range")) {
1549         QSlider* slider = new QSlider (parent);
1550         slider->setMinimum(getAttribute (QString ("START")).toInt ());
1551         slider->setMaximum(getAttribute (Ids::attr_end).toInt ());
1552         slider->setPageStep(1);
1553         slider->setOrientation(Qt::Horizontal);
1554         slider->setValue(value.toInt ());
1555         w = slider;
1556     } else if (!strcmp (ctype, "num") || !strcmp (ctype,  "string")) {
1557         w = new QLineEdit (value, parent);
1558     } else if (!strcmp (ctype, "bool")) {
1559         QCheckBox * checkbox = new QCheckBox (parent);
1560         checkbox->setChecked (value.toInt ());
1561         w = checkbox;
1562     } else if (!strcmp (ctype, "enum")) {
1563         QComboBox * combo = new QComboBox (parent);
1564         for (Node *e = firstChild (); e; e = e->nextSibling ())
1565             if (e->isElementNode () && !strcmp (e->nodeName (), "item"))
1566                 combo->addItem (static_cast <Element *> (e)->getAttribute (Ids::attr_value));
1567         combo->setCurrentIndex (value.toInt ());
1568         w = combo;
1569     } else if (!strcmp (ctype, "tree")) {
1570     } else
1571         qCDebug(LOG_KMPLAYER_COMMON) << "Unknown type:" << ctype;
1572     return w;
1573 }
1574 
1575 void TypeNode::changedXML (QTextStream & out) {
1576     if (!w) return;
1577     QByteArray ba = getAttribute (Ids::attr_type).toAscii ();
1578     const char *ctype = ba.constData ();
1579     QString value = getAttribute (Ids::attr_value);
1580     QString newvalue;
1581     if (!strcmp (ctype, "range")) {
1582         newvalue = QString::number (static_cast <QSlider *> (w)->value ());
1583     } else if (!strcmp (ctype, "num") || !strcmp (ctype,  "string")) {
1584         newvalue = static_cast <QLineEdit *> (w)->text ();
1585     } else if (!strcmp (ctype, "bool")) {
1586         newvalue = QString::number (static_cast <QCheckBox *> (w)->isChecked());
1587     } else if (!strcmp (ctype, "enum")) {
1588         newvalue = QString::number (static_cast<QComboBox *>(w)->currentIndex());
1589     } else if (!strcmp (ctype, "tree")) {
1590     } else
1591         qCDebug(LOG_KMPLAYER_COMMON) << "Unknown type:" << ctype;
1592     if (value != newvalue) {
1593         value = newvalue;
1594         setAttribute (Ids::attr_value, newvalue);
1595         out << outerXML ();
1596     }
1597 }
1598 
1599 //-----------------------------------------------------------------------------
1600 
1601 static const char * ffmpeg_supports [] = {
1602     "tvsource", "urlsource", nullptr
1603 };
1604 
1605 FFMpegProcessInfo::FFMpegProcessInfo (MediaManager *mgr)
1606  : ProcessInfo ("ffmpeg", i18n ("&FFMpeg"), ffmpeg_supports, mgr, nullptr) {}
1607 
1608 IProcess *FFMpegProcessInfo::create (PartBase *p, ProcessUser *usr) {
1609     FFMpeg *m = new FFMpeg (p, this, p->settings ());
1610     m->setSource (p->source ());
1611     m->user = usr;
1612     p->processCreated (m);
1613     return m;
1614 }
1615 
1616 FFMpeg::FFMpeg (QObject *parent, ProcessInfo *pinfo, Settings * settings)
1617  : Process (parent, pinfo, settings) {
1618 }
1619 
1620 FFMpeg::~FFMpeg () {
1621 }
1622 
1623 void FFMpeg::init () {
1624 }
1625 
1626 bool FFMpeg::deMediafiedPlay () {
1627     RecordDocument *rd = recordDocument (user);
1628     if (!rd)
1629         return false;
1630     initProcess ();
1631     connect (m_process, SIGNAL (finished (int, QProcess::ExitStatus)),
1632             this, SLOT (processStopped (int, QProcess::ExitStatus)));
1633     QString outurl = encodeFileOrUrl (rd->record_file);
1634     if (outurl.startsWith (QChar ('/')))
1635         QFile (outurl).remove ();
1636     QString exe ("ffmpeg ");
1637     QStringList args;
1638     if (!m_source->videoDevice ().isEmpty () ||
1639         !m_source->audioDevice ().isEmpty ()) {
1640         if (!m_source->videoDevice ().isEmpty ())
1641             args << "-vd" << m_source->videoDevice ();
1642         else
1643             args << "-vn";
1644         if (!m_source->audioDevice ().isEmpty ())
1645             args << "-ad" << m_source->audioDevice ();
1646         else
1647             args << "-an";
1648         QProcess process;
1649         QString ctl_exe ("v4lctl");
1650         QStringList ctl_args;
1651         if (!m_source->videoNorm ().isEmpty ()) {
1652             ctl_args << "-c" << m_source->videoDevice () << "setnorm" << m_source->videoNorm ();
1653             process.start (ctl_exe, ctl_args);
1654             process.waitForFinished (5000);
1655             args << "-tvstd" << m_source->videoNorm ();
1656         }
1657         if (m_source->frequency () > 0) {
1658             ctl_args.clear ();
1659             ctl_args << "-c" << m_source->videoDevice () << "setfreq" << QString::number (m_source->frequency ());
1660             process.start (ctl_exe, ctl_args);
1661             process.waitForFinished (5000);
1662         }
1663     } else {
1664         args << "-i" << encodeFileOrUrl (m_url);
1665     }
1666     args << KShell::splitArgs (m_settings->ffmpegarguments);
1667     args << outurl;
1668     qCDebug(LOG_KMPLAYER_COMMON, "ffmpeg %s\n", args.join (" ").toLocal8Bit().constData ());
1669     // FIXME if (m_player->source () == source) // ugly
1670     //    m_player->stop ();
1671     m_process->start (exe, args);
1672     if (m_process->waitForStarted ()) {
1673         setState (Playing);
1674         return true;
1675     }
1676     stop ();
1677     return false;
1678 }
1679 
1680 void FFMpeg::stop () {
1681     terminateJobs ();
1682     if (!running ())
1683         return;
1684     qCDebug(LOG_KMPLAYER_COMMON) << "FFMpeg::stop";
1685     m_process->write ("q");
1686 }
1687 
1688 void FFMpeg::quit () {
1689     stop ();
1690     if (!running ())
1691         return;
1692     if (m_process->waitForFinished (2000))
1693         Process::quit ();
1694 }
1695 
1696 void FFMpeg::processStopped (int, QProcess::ExitStatus) {
1697     setState (IProcess::NotRunning);
1698 }
1699 
1700 //-----------------------------------------------------------------------------
1701 
1702 static const char *npp_supports [] = { "urlsource", nullptr };
1703 
1704 NppProcessInfo::NppProcessInfo (MediaManager *mgr)
1705  : ProcessInfo ("npp", i18n ("&Ice Ape"), npp_supports, mgr, nullptr) {}
1706 
1707 IProcess *NppProcessInfo::create (PartBase *p, ProcessUser *usr) {
1708     NpPlayer *n = new NpPlayer (p, this, p->settings());
1709     n->setSource (p->source ());
1710     n->user = usr;
1711     p->processCreated (n);
1712     return n;
1713 }
1714 
1715 #ifdef KMPLAYER_WITH_NPP
1716 
1717 NpStream::NpStream (NpPlayer *p, uint32_t sid, const QString &u, const QByteArray &ps)
1718  : QObject (p),
1719    url (u),
1720    post (ps),
1721    job (nullptr), bytes (0),
1722    stream_id (sid),
1723    content_length (0),
1724    finish_reason (NoReason),
1725    received_data (false) {
1726     data_arrival.tv_sec = 0;
1727     (void) new StreamAdaptor (this);
1728     QString objpath = QString ("%1/stream_%2").arg (p->objectPath ()).arg (sid);
1729     QDBusConnection::sessionBus().registerObject (objpath, this);
1730 }
1731 
1732 NpStream::~NpStream () {
1733     close ();
1734 }
1735 
1736 void NpStream::open () {
1737     qCDebug(LOG_KMPLAYER_COMMON) << "NpStream " << stream_id << " open " << url;
1738     if (url.startsWith ("javascript:")) {
1739         NpPlayer *npp = static_cast <NpPlayer *> (parent ());
1740         QString result = npp->evaluate (url.mid (11), false);
1741         if (!result.isEmpty ()) {
1742             QByteArray cr = result.toLocal8Bit ();
1743             int len = strlen (cr.constData ());
1744             pending_buf.resize (len + 1);
1745             memcpy (pending_buf.data (), cr.constData (), len);
1746             pending_buf.data ()[len] = 0;
1747             gettimeofday (&data_arrival, nullptr);
1748         }
1749         qCDebug(LOG_KMPLAYER_COMMON) << "result is " << pending_buf.constData ();
1750         finish_reason = BecauseDone;
1751         emit stateChanged ();
1752     } else {
1753         if (!post.size ()) {
1754             job = KIO::get (KUrl (url), KIO::NoReload, KIO::HideProgressInfo);
1755             job->addMetaData ("PropagateHttpHeader", "true");
1756         } else {
1757             QStringList name;
1758             QStringList value;
1759             QString buf;
1760             int data_pos = -1;
1761             for (int i = 0; i < post.size () && data_pos < 0; ++i) {
1762                 char c = post.at (i);
1763                 switch (c) {
1764                 case ':':
1765                     if (name.size () == value.size ()) {
1766                         name << buf;
1767                         buf.truncate (0);
1768                     } else
1769                         buf += QChar (':');
1770                     break;
1771                 case '\r':
1772                     break;
1773                 case '\n':
1774                     if (name.size () == value.size ()) {
1775                         if (buf.isEmpty ()) {
1776                             data_pos = i + 1;
1777                         } else {
1778                             name << buf;
1779                             value << QString ("");
1780                         }
1781                     } else {
1782                         value << buf;
1783                     }
1784                     buf.truncate (0);
1785                     break;
1786                 default:
1787                     buf += QChar (c);
1788                 }
1789             }
1790             job = KIO::http_post (KUrl (url), post.mid (data_pos), KIO::HideProgressInfo);
1791             for (int i = 0; i < name.size (); ++i)
1792                 job->addMetaData (name[i].trimmed (), value[i].trimmed ());
1793         }
1794         job->addMetaData ("errorPage", "false");
1795         connect (job, SIGNAL (data (KIO::Job *, const QByteArray &)),
1796                 this, SLOT (slotData (KIO::Job *, const QByteArray &)));
1797         connect (job, SIGNAL (result (KJob *)),
1798                 this, SLOT (slotResult (KJob *)));
1799         connect (job, SIGNAL(redirection(KIO::Job*, const QUrl&)),
1800                 this, SLOT (redirection (KIO::Job *, const QUrl &)));
1801         connect (job, SIGNAL (mimetype (KIO::Job *, const QString &)),
1802                 SLOT (slotMimetype (KIO::Job *, const QString &)));
1803         connect (job, SIGNAL (totalSize (KJob *, qulonglong)),
1804                 SLOT (slotTotalSize (KJob *, qulonglong)));
1805     }
1806 }
1807 
1808 void NpStream::close () {
1809     if (job) {
1810         job->kill (); // quiet, no result signal
1811         job = nullptr;
1812         finish_reason = BecauseStopped;
1813         // don't emit stateChanged(), because always triggered from NpPlayer
1814     }
1815 }
1816 
1817 void NpStream::destroy () {
1818     pending_buf.clear ();
1819     static_cast <NpPlayer *> (parent ())->destroyStream (stream_id);
1820 }
1821 
1822 void NpStream::slotResult (KJob *jb) {
1823     qCDebug(LOG_KMPLAYER_COMMON) << "slotResult " << stream_id << " " << bytes << " err:" << jb->error ();
1824     finish_reason = jb->error () ? BecauseError : BecauseDone;
1825     job = nullptr; // signal KIO::Job::result deletes itself
1826     emit stateChanged ();
1827 }
1828 
1829 void NpStream::slotData (KIO::Job*, const QByteArray& qb) {
1830     if (job) {
1831         int sz = pending_buf.size ();
1832         if (sz) {
1833             pending_buf.resize (sz + qb.size ());
1834             memcpy (pending_buf.data () + sz, qb.constData (), qb.size ());
1835         } else {
1836             pending_buf = qb;
1837         }
1838         if (sz + qb.size () > 64000 &&
1839                 !job->isSuspended () && !job->suspend ())
1840             qCCritical(LOG_KMPLAYER_COMMON) << "suspend not supported" << endl;
1841         if (!sz)
1842             gettimeofday (&data_arrival, nullptr);
1843         if (!received_data) {
1844             received_data = true;
1845             http_headers = job->queryMetaData ("HTTP-Headers");
1846             if (!http_headers.isEmpty() && !http_headers.endsWith (QChar ('\n')))
1847                 http_headers += QChar ('\n');
1848         }
1849         if (sz + qb.size ())
1850             emit stateChanged ();
1851     }
1852 }
1853 
1854 void NpStream::redirection (KIO::Job*, const QUrl& kurl) {
1855     url = kurl.url ();
1856     emit redirected (stream_id, kurl);
1857 }
1858 
1859 void NpStream::slotMimetype (KIO::Job *, const QString &mime) {
1860     mimetype = mime.indexOf("adobe.flash") > -1 ? "application/x-shockwave-flash" : mime;
1861 }
1862 
1863 void NpStream::slotTotalSize (KJob *, qulonglong sz) {
1864     content_length = sz;
1865 }
1866 
1867 NpPlayer::NpPlayer (QObject *parent, ProcessInfo *pinfo, Settings *settings)
1868  : Process (parent, pinfo, settings),
1869    write_in_progress (false),
1870    in_process_stream (false) {
1871 }
1872 
1873 NpPlayer::~NpPlayer () {
1874 }
1875 
1876 void NpPlayer::init () {
1877 }
1878 
1879 void NpPlayer::initProcess () {
1880     setupProcess (&m_process);
1881     m_process_state = QProcess::NotRunning;
1882     connect (m_process, SIGNAL (finished (int, QProcess::ExitStatus)),
1883             this, SLOT (processStopped (int, QProcess::ExitStatus)));
1884     connect (m_process, SIGNAL (readyReadStandardError ()),
1885             this, SLOT (processOutput ()));
1886     connect (m_process, SIGNAL (bytesWritten (qint64)),
1887             this, SLOT (wroteStdin (qint64)));
1888     if (iface.isEmpty ()) {
1889         iface = QString ("org.kde.kmplayer.callback");
1890         static int count = 0;
1891         path = QString ("/npplayer%1").arg (count++);
1892         (void) new CallbackAdaptor (this);
1893         QDBusConnection::sessionBus().registerObject (path, this);
1894         filter = QString ("type='method_call',interface='org.kde.kmplayer.callback'");
1895         service = QDBusConnection::sessionBus().baseService ();
1896         //service = QString (dbus_bus_get_unique_name (conn));
1897         qCDebug(LOG_KMPLAYER_COMMON) << "using service " << service << " interface " << iface << " filter:" << filter;
1898     }
1899 }
1900 
1901 bool NpPlayer::deMediafiedPlay () {
1902     qCDebug(LOG_KMPLAYER_COMMON) << "NpPlayer::play '" << m_url << "' state " << m_state;
1903     // if we change from XPLAIN to XEMBED, the DestroyNotify may come later
1904     if (!view ())
1905         return false;
1906     if (!m_url.isEmpty () && m_url != "about:empty") {
1907         QDBusMessage msg = QDBusMessage::createMethodCall (
1908                 remote_service, "/plugin", "org.kde.kmplayer.backend", "play");
1909         msg << m_url;
1910         msg.setDelayedReply (false);
1911         QDBusConnection::sessionBus().send (msg);
1912         setState (IProcess::Buffering);
1913     }
1914     return true;
1915 }
1916 
1917 bool NpPlayer::ready () {
1918     Mrl *node = mrl ();
1919     if (!node || !user || !user->viewer ())
1920         return false;
1921 
1922     user->viewer ()->useIndirectWidget (false);
1923     user->viewer ()->setMonitoring (IViewer::MonitorNothing);
1924 
1925     if (state () == IProcess::Ready)
1926         return true;
1927 
1928     initProcess ();
1929     QString exe ("knpplayer");
1930     QStringList args;
1931     args << "-cb" << (service + path);
1932     args << "-wid" << QString::number (user->viewer ()->windowHandle ());
1933     startProcess (exe, args);
1934     if (m_process->waitForStarted (5000)) {
1935         QString s;
1936         for (int i = 0; i < 2 && remote_service.isEmpty (); ++i) {
1937             if (!m_process->waitForReadyRead (5000))
1938                 return false;
1939             const QByteArray ba = m_process->readAllStandardOutput ();
1940             s += QString::fromAscii (ba.data (), ba.size ());
1941             int nl = s.indexOf (QChar ('\n'));
1942             if (nl > 0) {
1943                 int p = s.indexOf ("NPP_DBUS_SRV=");
1944                 if (p > -1)
1945                     remote_service = s.mid (p + 13, nl - p - 13);
1946             }
1947         }
1948     }
1949     connect (m_process, SIGNAL (readyReadStandardOutput ()),
1950             this, SLOT (processOutput ()));
1951     if (!remote_service.isEmpty ()) {
1952         QString mime = "text/plain";
1953         QString plugin;
1954         Element *elm = node;
1955         if (elm->id == id_node_html_object) {
1956             // this sucks to have to do this here ..
1957             for (Node *n = elm->firstChild (); n; n = n->nextSibling ())
1958                 if (n->id == KMPlayer::id_node_html_embed) {
1959                     elm = static_cast <Element *> (n);
1960                     break;
1961                 }
1962         }
1963         for (Node *n = mrl (); n; n = n->parentNode ()) {
1964             Mrl *mrl = n->mrl ();
1965             if (mrl && !mrl->mimetype.isEmpty ()) {
1966                 plugin = m_source->plugin (mrl->mimetype);
1967                 qCDebug(LOG_KMPLAYER_COMMON) << "search plugin " << mrl->mimetype << "->" << plugin;
1968                 if (!plugin.isEmpty ()) {
1969                     mime = mrl->mimetype;
1970                     if ( mime.indexOf("adobe.flash") > -1 )
1971                         mime = "application/x-shockwave-flash";
1972                     break;
1973                 }
1974             }
1975         }
1976         if (!plugin.isEmpty ()) {
1977             QDBusMessage msg = QDBusMessage::createMethodCall (
1978                     remote_service, "/plugin", "org.kde.kmplayer.backend", "setup");
1979             msg << mime << plugin;
1980             QMap <QString, QVariant> urlargs;
1981             for (AttributePtr a = elm->attributes ().first (); a; a = a->nextSibling ())
1982                 urlargs.insert (a->name ().toString (), a->value ());
1983             msg << urlargs;
1984             msg.setDelayedReply (false);
1985             QDBusConnection::sessionBus().call (msg, QDBus::BlockWithGui);
1986             setState (IProcess::Ready);
1987             return true;
1988         }
1989     }
1990     m_old_state = m_state = Ready;
1991     stop ();
1992 
1993     return false;
1994 }
1995 
1996 void NpPlayer::running (const QString &srv) {
1997     remote_service = srv;
1998     qCDebug(LOG_KMPLAYER_COMMON) << "NpPlayer::running " << srv;
1999     setState (Ready);
2000 }
2001 
2002 void NpPlayer::plugged () {
2003     view ()->videoStart ();
2004 }
2005 
2006 static int getStreamId (const QString &path) {
2007     int p = path.lastIndexOf (QChar ('_'));
2008     if (p < 0) {
2009         qCCritical(LOG_KMPLAYER_COMMON) << "wrong object path " << path << endl;
2010         return -1;
2011     }
2012     bool ok;
2013     qint32 sid = path.mid (p+1).toInt (&ok);
2014     if (!ok) {
2015         qCCritical(LOG_KMPLAYER_COMMON) << "wrong object path suffix " << path.mid (p+1) << endl;
2016         return -1;
2017     }
2018     return sid;
2019 }
2020 
2021 void NpPlayer::request_stream (const QString &path, const QString &url, const QString &target, const QByteArray &post) {
2022     QString uri (url);
2023     qCDebug(LOG_KMPLAYER_COMMON) << "NpPlayer::request " << path << " '" << url << "' " << " tg:" << target << "post" << post.size ();
2024     bool js = url.startsWith ("javascript:");
2025     if (!js) {
2026         QString base = process_info->manager->player ()->docBase ().url ();
2027         uri = KUrl (base.isEmpty () ? m_url : base, url).url ();
2028     }
2029     qCDebug(LOG_KMPLAYER_COMMON) << "NpPlayer::request " << path << " '" << uri << "'" << m_url << "->" << url;
2030     qint32 sid = getStreamId (path);
2031     if ((int)sid >= 0) {
2032         if (!target.isEmpty ()) {
2033             qCDebug(LOG_KMPLAYER_COMMON) << "new page request " << target;
2034             if (js) {
2035                 QString result = evaluate (url.mid (11), false);
2036                 qCDebug(LOG_KMPLAYER_COMMON) << "result is " << result;
2037                 if (result == "undefined")
2038                     uri = QString ();
2039                 else
2040                     uri = KUrl (m_url, result).url (); // probably wrong ..
2041             }
2042             KUrl kurl(uri);
2043             if (kurl.isValid ())
2044                 process_info->manager->player ()->openUrl (kurl, target, QString ());
2045             sendFinish (sid, 0, NpStream::BecauseDone);
2046         } else {
2047             NpStream * ns = new NpStream (this, sid, uri, post);
2048             connect (ns, SIGNAL (stateChanged ()), this, SLOT (streamStateChanged ()));
2049             streams[sid] = ns;
2050             if (url != uri)
2051                 streamRedirected (sid, uri);
2052             if (!write_in_progress)
2053                 processStreams ();
2054         }
2055     }
2056 }
2057 
2058 QString NpPlayer::evaluate (const QString &script, bool store) {
2059     QString result ("undefined");
2060     emit evaluate (script, store, result);
2061     //qCDebug(LOG_KMPLAYER_COMMON) << "evaluate " << script << " => " << result;
2062     return result;
2063 }
2064 
2065 void NpPlayer::dimension (int w, int h) {
2066     source ()->setAspect (mrl (), 1.0 * w/ h);
2067 }
2068 
2069 void NpPlayer::destroyStream (uint32_t sid) {
2070     if (streams.contains (sid)) {
2071         NpStream *ns = streams[sid];
2072         ns->close ();
2073         if (!write_in_progress)
2074             processStreams ();
2075     } else {
2076         qCWarning(LOG_KMPLAYER_COMMON) << "Object " << sid << " not found";
2077     }
2078     if (!sid)
2079         emit loaded ();
2080 }
2081 
2082 void NpPlayer::sendFinish (quint32 sid, quint32 bytes, NpStream::Reason because) {
2083     qCDebug(LOG_KMPLAYER_COMMON) << "NpPlayer::sendFinish " << sid << " bytes:" << bytes;
2084     if (running ()) {
2085         uint32_t reason = (int) because;
2086         QString objpath = QString ("/stream_%1").arg (sid);
2087         QDBusMessage msg = QDBusMessage::createMethodCall (
2088                 remote_service, objpath, "org.kde.kmplayer.backend", "eof");
2089         msg << bytes << reason;
2090         msg.setDelayedReply (false);
2091         QDBusConnection::sessionBus().send (msg);
2092     }
2093     if (!sid)
2094         emit loaded ();
2095 }
2096 
2097 void NpPlayer::terminateJobs () {
2098     Process::terminateJobs ();
2099     const StreamMap::iterator e = streams.end ();
2100     for (StreamMap::iterator i = streams.begin (); i != e; ++i)
2101         delete i.value ();
2102     streams.clear ();
2103 }
2104 
2105 void NpPlayer::stop () {
2106     terminateJobs ();
2107     if (!running ())
2108         return;
2109     qCDebug(LOG_KMPLAYER_COMMON) << "NpPlayer::stop ";
2110     QDBusMessage msg = QDBusMessage::createMethodCall (
2111             remote_service, "/plugin", "org.kde.kmplayer.backend", "quit");
2112     msg.setDelayedReply (false);
2113     QDBusConnection::sessionBus().send (msg);
2114 }
2115 
2116 void NpPlayer::quit () {
2117     if (running () && !m_process->waitForFinished (2000))
2118         Process::quit ();
2119 }
2120 
2121 void NpPlayer::processOutput () {
2122     if (!remote_service.isEmpty ())
2123         outputToView (view (), m_process->readAllStandardOutput ());
2124     outputToView (view (), m_process->readAllStandardError ());
2125 }
2126 
2127 void NpPlayer::processStopped (int, QProcess::ExitStatus) {
2128     terminateJobs ();
2129     if (m_source)
2130         m_source->document ()->message (MsgInfoString, nullptr);
2131     setState (IProcess::NotRunning);
2132 }
2133 
2134 void NpPlayer::streamStateChanged () {
2135     setState (IProcess::Playing); // hmm, this doesn't really fit in current states
2136     if (!write_in_progress)
2137         processStreams ();
2138 }
2139 
2140 void NpPlayer::streamRedirected(uint32_t sid, const QUrl& u) {
2141     if (running ()) {
2142         qCDebug(LOG_KMPLAYER_COMMON) << "redirected " << sid << " to " << u.url();
2143         QString objpath = QString ("/stream_%1").arg (sid);
2144         QDBusMessage msg = QDBusMessage::createMethodCall (
2145                 remote_service, objpath, "org.kde.kmplayer.backend", "redirected");
2146         msg << u.url ();
2147         msg.setDelayedReply (false);
2148         QDBusConnection::sessionBus().send (msg);
2149     }
2150 }
2151 
2152 void NpPlayer::requestGet (const uint32_t id, const QString &prop, QString *res) {
2153     if (!remote_service.isEmpty ()) {
2154         QDBusMessage msg = QDBusMessage::createMethodCall (
2155                 remote_service, "/plugin", "org.kde.kmplayer.backend", "get");
2156         msg << id << prop;
2157         QDBusMessage rmsg = QDBusConnection::sessionBus().call (msg, QDBus::BlockWithGui);
2158         if (rmsg.type () == QDBusMessage::ReplyMessage) {
2159             //qCDebug(LOG_KMPLAYER_COMMON) << "get " << prop << rmsg.arguments ().size ();
2160             if (rmsg.arguments ().size ()) {
2161                 QString s = rmsg.arguments ().first ().toString ();
2162                 if (s != "error")
2163                     *res = s;
2164             }
2165         } else {
2166             qCCritical(LOG_KMPLAYER_COMMON) << "get" << prop << rmsg.type () << rmsg.errorMessage ();
2167         }
2168     }
2169 }
2170 
2171 void NpPlayer::requestCall (const uint32_t id, const QString &func,
2172         const QStringList &args, QString *res) {
2173     QDBusMessage msg = QDBusMessage::createMethodCall (
2174             remote_service, "/plugin", "org.kde.kmplayer.backend", "call");
2175     msg << id << func << args;
2176     QDBusMessage rmsg = QDBusConnection::sessionBus().call (msg, QDBus::BlockWithGui);
2177     //qCDebug(LOG_KMPLAYER_COMMON) << "call " << func << rmsg.arguments ().size ();
2178     if (rmsg.arguments ().size ()) {
2179         QString s = rmsg.arguments ().first ().toString ();
2180         if (s != "error")
2181             *res = s;
2182     }
2183 }
2184 
2185 void NpPlayer::processStreams () {
2186     NpStream *stream = nullptr;
2187     qint32 stream_id;
2188     timeval tv = { 0x7fffffff, 0 };
2189     const StreamMap::iterator e = streams.end ();
2190     int active_count = 0;
2191 
2192     if (in_process_stream || write_in_progress) {
2193         qCCritical(LOG_KMPLAYER_COMMON) << "wrong call"; // TODO: port << kBacktrace();
2194         return;
2195     }
2196     in_process_stream = true;
2197 
2198     //qCDebug(LOG_KMPLAYER_COMMON) << "NpPlayer::processStreams " << streams.size ();
2199     for (StreamMap::iterator i = streams.begin (); i != e;) {
2200         NpStream *ns = i.value ();
2201         if (ns->job) {
2202             active_count++;
2203         } else if (active_count < 5 &&
2204                 ns->finish_reason == NpStream::NoReason) {
2205             write_in_progress = true; // javascript: urls emit stateChange
2206             ns->open ();
2207             write_in_progress = false;
2208             if (ns->job) {
2209                 connect(ns, SIGNAL(redirected(uint32_t, const QUrl&)),
2210                         this, SLOT(streamRedirected(uint32_t, const QUrl&)));
2211                 active_count++;
2212             }
2213         }
2214         if (ns->finish_reason == NpStream::BecauseStopped ||
2215                 ns->finish_reason == NpStream::BecauseError ||
2216                 (ns->finish_reason == NpStream::BecauseDone &&
2217                  ns->pending_buf.size () == 0)) {
2218             sendFinish (i.key(), ns->bytes, ns->finish_reason);
2219             i = streams.erase (i);
2220             delete ns;
2221         } else {
2222             if (ns->pending_buf.size () > 0 &&
2223                     (ns->data_arrival.tv_sec < tv.tv_sec ||
2224                      (ns->data_arrival.tv_sec == tv.tv_sec &&
2225                       ns->data_arrival.tv_usec < tv.tv_usec))) {
2226                 tv = ns->data_arrival;
2227                 stream = ns;
2228                 stream_id = i.key();
2229             }
2230             ++i;
2231         }
2232     }
2233     //qCDebug(LOG_KMPLAYER_COMMON) << "NpPlayer::processStreams " << stream;
2234     if (stream) {
2235         if (stream->finish_reason != NpStream::BecauseStopped &&
2236                 stream->finish_reason != NpStream::BecauseError &&
2237                 !stream->bytes &&
2238                 (!stream->mimetype.isEmpty() || stream->content_length)) {
2239             QString objpath = QString ("/stream_%1").arg (stream->stream_id);
2240             QDBusMessage msg = QDBusMessage::createMethodCall (
2241                     remote_service, objpath, "org.kde.kmplayer.backend", "streamInfo");
2242             msg << stream->mimetype
2243                 << stream->content_length
2244                 << stream->http_headers;
2245             msg.setDelayedReply (false);
2246             QDBusConnection::sessionBus().send (msg);
2247         }
2248         const int header_len = 2 * sizeof (qint32);
2249         qint32 chunk = stream->pending_buf.size ();
2250         send_buf.resize (chunk + header_len);
2251         memcpy (send_buf.data (), &stream_id, sizeof (qint32));
2252         memcpy (send_buf.data() + sizeof (qint32), &chunk, sizeof (qint32));
2253         memcpy (send_buf.data ()+header_len,
2254                 stream->pending_buf.constData (), chunk);
2255         stream->pending_buf = QByteArray ();
2256         /*fprintf (stderr, " => %d %d\n", (long)stream_id, chunk);*/
2257         stream->bytes += chunk;
2258         write_in_progress = true;
2259         m_process->write (send_buf);
2260         if (stream->finish_reason == NpStream::NoReason)
2261             stream->job->resume ();
2262     }
2263     in_process_stream = false;
2264 }
2265 
2266 void NpPlayer::wroteStdin (qint64) {
2267     if (!m_process->bytesToWrite ()) {
2268         write_in_progress = false;
2269         if (running ())
2270             processStreams ();
2271     }
2272 }
2273 
2274 QString NpPlayer::cookie (const QString &url)
2275 {
2276     QString s;
2277     View *v = view ();
2278     if (v) {
2279         KIO::Integration::CookieJar jar (v);
2280         jar.setWindowId (v->topLevelWidget()->winId ());
2281         QList<QNetworkCookie> c = jar.cookiesForUrl (url);
2282         QList<QNetworkCookie>::const_iterator e = c.constEnd ();
2283         for (QList<QNetworkCookie>::const_iterator i = c.constBegin (); i != e; ++i)
2284             s += (s.isEmpty() ? "" : ";") + QString::fromUtf8 ((*i).toRawForm());
2285     }
2286     return s;
2287 }
2288 
2289 #else
2290 
2291 NpStream::NpStream (NpPlayer *p, uint32_t sid, const QString &u, const QByteArray &/*ps*/)
2292     : QObject (p) {}
2293 
2294 NpStream::~NpStream () {}
2295 void NpStream::slotResult (KJob*) {}
2296 void NpStream::slotData (KIO::Job*, const QByteArray&) {}
2297 void NpStream::redirection(KIO::Job*, const QUrl&) {}
2298 void NpStream::slotMimetype (KIO::Job *, const QString &) {}
2299 void NpStream::slotTotalSize (KJob *, KIO::filesize_t) {}
2300 
2301 NpPlayer::NpPlayer (QObject *parent, ProcessInfo *pinfo, Settings *settings)
2302  : Process (parent, pinfo, settings) {}
2303 NpPlayer::~NpPlayer () {}
2304 void NpPlayer::init () {}
2305 bool NpPlayer::deMediafiedPlay () { return false; }
2306 void NpPlayer::initProcess () {}
2307 void NpPlayer::stop () {}
2308 void NpPlayer::quit () { }
2309 bool NpPlayer::ready () { return false; }
2310 void NpPlayer::requestGet (const uint32_t, const QString &, QString *) {}
2311 void NpPlayer::requestCall (const uint32_t, const QString &, const QStringList &, QString *) {}
2312 void NpPlayer::processOutput () {}
2313 void NpPlayer::processStopped (int, QProcess::ExitStatus) {}
2314 void NpPlayer::wroteStdin (qint64) {}
2315 void NpPlayer::streamStateChanged () {}
2316 void NpPlayer::streamRedirected(uint32_t, const QUrl&) {}
2317 void NpPlayer::terminateJobs () {}
2318 
2319 #endif