File indexing completed on 2024-04-21 04:54:09

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