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

0001 /*
0002     This file belong to the KMPlayer project, a movie player plugin for Konqueror
0003     SPDX-FileCopyrightText: 2007 Koos Vriezen <koos.vriezen@gmail.com>
0004 
0005     SPDX-License-Identifier: LGPL-2.0-or-later
0006 */
0007 
0008 #include <QTextStream>
0009 #include <QApplication>
0010 #include <QMovie>
0011 #include <QBuffer>
0012 #include <QPainter>
0013 #include <QSvgRenderer>
0014 #include <QImage>
0015 #include <QFile>
0016 #include <QUrl>
0017 #include <QTextCodec>
0018 #include <QTextStream>
0019 #include <QMimeDatabase>
0020 #include <QMimeType>
0021 
0022 #include <KLocalizedString>
0023 #include <KIO/Job>
0024 #include <KUrlAuthorized>
0025 
0026 #include "mediaobject.h"
0027 #include "kmplayerprocess.h"
0028 #include "kmplayerview.h"
0029 #include "expression.h"
0030 #include "viewarea.h"
0031 #include "kmplayerpartbase.h"
0032 #include "kmplayercommon_log.h"
0033 
0034 using namespace KMPlayer;
0035 
0036 namespace {
0037 
0038     typedef QMap <QString, ImageDataPtrW> ImageDataMap;
0039 
0040     static DataCache *memory_cache;
0041     static ImageDataMap *image_data_map;
0042 
0043     struct GlobalMediaData : public GlobalShared<GlobalMediaData> {
0044         GlobalMediaData (GlobalMediaData **gb)
0045          : GlobalShared<GlobalMediaData> (gb) {
0046             memory_cache = new DataCache;
0047             image_data_map = new ImageDataMap;
0048         }
0049         ~GlobalMediaData () override;
0050     };
0051 
0052     static GlobalMediaData *global_media;
0053 
0054     GlobalMediaData::~GlobalMediaData () {
0055         delete memory_cache;
0056         delete image_data_map;
0057         global_media = nullptr;
0058     }
0059 
0060     bool isBufferBinaryData(const QByteArray &data)
0061     {
0062         // Check the first 32 bytes (see shared-mime spec)
0063         const char *p = data.data();
0064         const int end = qMin(32, data.size());
0065         for (int i = 0; i < end; ++i) {
0066             if ((unsigned char)(p[i]) < 32 && p[i] != 9 && p[i] != 10 && p[i] != 13) { // ASCII control character
0067                 return true;
0068             }
0069         }
0070         return false;
0071     }
0072 
0073 }
0074 
0075 //------------------------%<----------------------------------------------------
0076 
0077 MediaManager::MediaManager (PartBase *player) : m_player (player) {
0078     if (!global_media)
0079         (void) new GlobalMediaData (&global_media);
0080     else
0081         global_media->ref ();
0082 
0083     m_process_infos ["mplayer"] = new MPlayerProcessInfo (this);
0084     m_process_infos ["phonon"] = new PhononProcessInfo (this);
0085     //XineProcessInfo *xpi = new XineProcessInfo (this);
0086     //m_process_infos ["xine"] = xpi;
0087     //m_process_infos ["gstreamer"] = new GStreamer (this, m_settings);, i18n ("&GStreamer")
0088 #ifdef KMPLAYER_WITH_NPP
0089     m_process_infos ["npp"] = new NppProcessInfo (this);
0090 #endif
0091     m_record_infos ["mencoder"] = new MEncoderProcessInfo (this);
0092     m_record_infos ["mplayerdumpstream"] = new MPlayerDumpProcessInfo (this);
0093     m_record_infos ["ffmpeg"] = new FFMpegProcessInfo (this);
0094     //m_record_infos ["xine"] = xpi;
0095 }
0096 
0097 MediaManager::~MediaManager () {
0098     for (ProcessList::iterator i = m_processes.begin ();
0099             i != m_processes.end ();
0100             i = m_processes.begin () /*~Process removes itself from this list*/)
0101     {
0102         qCDebug(LOG_KMPLAYER_COMMON) << "~MediaManager " << *i << endl;
0103         delete *i;
0104     }
0105     for (ProcessList::iterator i = m_recorders.begin ();
0106             i != m_recorders.end ();
0107             i = m_recorders.begin ())
0108     {
0109         qCDebug(LOG_KMPLAYER_COMMON) << "~MediaManager " << *i << endl;
0110         delete *i;
0111     }
0112     const ProcessInfoMap::iterator ie = m_process_infos.end ();
0113     for (ProcessInfoMap::iterator i = m_process_infos.begin (); i != ie; ++i)
0114         if (!m_record_infos.contains (i.key ()))
0115             delete i.value ();
0116 
0117     const ProcessInfoMap::iterator rie = m_record_infos.end ();
0118     for (ProcessInfoMap::iterator i = m_record_infos.begin (); i != rie; ++i)
0119         delete i.value ();
0120 
0121     if (m_media_objects.size ()) {
0122         qCCritical(LOG_KMPLAYER_COMMON) << "~MediaManager media list not empty " << m_media_objects.size () << endl;
0123         // bug elsewere, but don't crash
0124         const MediaList::iterator me = m_media_objects.end ();
0125         for (MediaList::iterator i = m_media_objects.begin (); i != me; ) {
0126             if (*i && (*i)->mrl () &&
0127                     (*i)->mrl ()->document ()->active ()) {
0128                 (*i)->mrl ()->document ()->deactivate ();
0129                 i = m_media_objects.begin ();
0130             } else {
0131                 ++i;
0132             }
0133         }
0134         if (m_media_objects.size ())
0135             qCCritical(LOG_KMPLAYER_COMMON) << "~MediaManager media list still not empty" << m_media_objects.size () << endl;
0136     }
0137     global_media->unref ();
0138 }
0139 
0140 MediaObject *MediaManager::createAVMedia (Node *node, const QByteArray &) {
0141     RecordDocument *rec = id_node_record_document == node->id
0142         ? convertNode <RecordDocument> (node)
0143         : nullptr;
0144     if (!rec && !m_player->source()->authoriseUrl (
0145                 node->mrl()->absolutePath ()))
0146         return nullptr;
0147 
0148     AudioVideoMedia *av = new AudioVideoMedia (this, node);
0149     if (rec) {
0150         av->process = m_record_infos[rec->recorder]->create (m_player, av);
0151         m_recorders.push_back (av->process);
0152         qCDebug(LOG_KMPLAYER_COMMON) << "Adding recorder " << endl;
0153     } else {
0154         av->process = m_process_infos[m_player->processName (
0155                 av->mrl ())]->create (m_player, av);
0156         m_processes.push_back (av->process);
0157     }
0158     av->process->user = av;
0159     av->setViewer (!rec
0160         ? m_player->viewWidget ()->viewArea ()->createVideoWidget ()
0161         : nullptr);
0162 
0163     if (av->process->state () <= IProcess::Ready)
0164         av->process->ready ();
0165     return av;
0166 }
0167 
0168 static const QString statemap [] = {
0169     i18n ("Not Running"), i18n ("Ready"), i18n ("Buffering"), i18n ("Playing"),  i18n ("Paused")
0170 };
0171 
0172 void MediaManager::stateChange (AudioVideoMedia *media,
0173         IProcess::State olds, IProcess::State news) {
0174     //p->viewer()->view()->controlPanel()->setPlaying(news > Process::Ready);
0175     Mrl *mrl = media->mrl ();
0176     qCDebug(LOG_KMPLAYER_COMMON) << "processState " << media->process->process_info->name << " "
0177         << statemap[olds] << " -> " << statemap[news];
0178 
0179     if (!mrl) { // document dispose
0180         if (IProcess::Ready < news)
0181             media->process->quit ();
0182         else
0183             delete media;
0184         return;
0185     }
0186 
0187     if (!m_player->view ()) // part destruction
0188         return;
0189 
0190     bool is_rec = id_node_record_document == mrl->id;
0191     m_player->updateStatus (i18n ("Player %1 %2",
0192                 media->process->process_info->name, statemap[news]));
0193     if (IProcess::Playing == news) {
0194         if (Element::state_deferred == mrl->state)
0195             mrl->undefer ();
0196         bool has_video = !is_rec;
0197         if (is_rec && m_recorders.contains(media->process))
0198             m_player->recorderPlaying ();
0199         if (has_video) {
0200             if (m_player->view ()) {
0201                 if (media->viewer ()) {
0202                     media->viewer ()->setAspect (mrl->aspect);
0203                     media->viewer ()->map ();
0204                 }
0205                 if (Mrl::SingleMode == mrl->view_mode)
0206                     m_player->viewWidget ()->viewArea ()->resizeEvent (nullptr);
0207             }
0208         }
0209     } else if (IProcess::NotRunning == news) {
0210         if (AudioVideoMedia::ask_delete == media->request) {
0211             delete media;
0212         } else if (mrl->unfinished ()) {
0213             mrl->document ()->post (mrl, new Posting (mrl, MsgMediaFinished));
0214         }
0215     } else if (IProcess::Ready == news) {
0216         if (AudioVideoMedia::ask_play == media->request) {
0217             playAudioVideo (media);
0218         } else if (AudioVideoMedia::ask_grab == media->request) {
0219             grabPicture (media);
0220         } else {
0221             if (!is_rec && Mrl::SingleMode == mrl->view_mode) {
0222                 ProcessList::ConstIterator i, e = m_processes.constEnd ();
0223                 for (i = m_processes.constBegin(); i != e; ++i)
0224                     if (*i != media->process &&
0225                             (*i)->state () == IProcess::Ready)
0226                         (*i)->play (); // delayed playing
0227                 e = m_recorders.constEnd ();
0228                 for (i = m_recorders.constBegin (); i != e; ++i)
0229                     if (*i != media->process &&
0230                             (*i)->state () == IProcess::Ready)
0231                         (*i)->play (); // delayed recording
0232             }
0233             if (AudioVideoMedia::ask_delete == media->request) {
0234                 delete media;
0235             } else if (olds > IProcess::Ready) {
0236                 if (is_rec)
0237                     mrl->message (MsgMediaFinished, nullptr); // FIXME
0238                 else
0239                     mrl->document()->post(mrl, new Posting (mrl, MsgMediaFinished));
0240             }
0241         }
0242     } else if (IProcess::Buffering == news) {
0243         if (AudioVideoMedia::ask_pause == media->request) {
0244             media->pause ();
0245         } else if (mrl->view_mode != Mrl::SingleMode) {
0246             mrl->defer (); // paused the SMIL
0247         }
0248     }
0249 }
0250 
0251 void MediaManager::playAudioVideo (AudioVideoMedia *media) {
0252     Mrl *mrl = media->mrl ();
0253     media->request = AudioVideoMedia::ask_nothing;
0254     if (!mrl ||!m_player->view ())
0255         return;
0256     if (Mrl::SingleMode == mrl->view_mode) {
0257         ProcessList::ConstIterator i, e = m_processes.constEnd ();
0258         for (i = m_processes.constBegin(); i != e; ++i)
0259             if (*i != media->process && (*i)->state () > IProcess::Ready)
0260                 return; // delay, avoiding two overlaping widgets
0261     }
0262     media->process->play ();
0263 }
0264 
0265 void MediaManager::grabPicture (AudioVideoMedia *media) {
0266     Mrl *mrl = media->mrl ();
0267     media->request = AudioVideoMedia::ask_nothing;
0268     if (!mrl)
0269         return;
0270     media->process->grabPicture (media->m_grab_file, media->m_frame);
0271 }
0272 
0273 void MediaManager::processDestroyed (IProcess *process) {
0274     qCDebug(LOG_KMPLAYER_COMMON) << "processDestroyed " << process << endl;
0275     m_processes.removeAll (process);
0276     m_recorders.removeAll (process);
0277 }
0278 
0279 //------------------------%<----------------------------------------------------
0280 
0281 MediaObject::MediaObject (MediaManager *manager, Node *node)
0282  : m_manager (manager), m_node (node) {
0283     manager->medias ().push_back (this);
0284 }
0285 
0286 MediaObject::~MediaObject () {
0287     m_manager->medias ().removeAll (this);
0288 }
0289 
0290 void MediaObject::destroy () {
0291     delete this;
0292 }
0293 
0294 Mrl *MediaObject::mrl () {
0295     return m_node ? m_node->mrl () : nullptr;
0296 }
0297 
0298 //------------------------%<----------------------------------------------------
0299 
0300 void DataCache::add (const QString & url, const QString &mime, const QByteArray & data) {
0301     QByteArray bytes;
0302     bytes = data;
0303     cache_map.insert (url, qMakePair (mime, bytes));
0304     preserve_map.remove (url);
0305     Q_EMIT preserveRemoved (url);
0306 }
0307 
0308 bool DataCache::get (const QString & url, QString &mime, QByteArray & data) {
0309     DataMap::const_iterator it = cache_map.constFind (url);
0310     if (it != cache_map.constEnd ()) {
0311         mime = it.value ().first;
0312         data = it.value ().second;
0313         return true;
0314     }
0315     return false;
0316 }
0317 
0318 bool DataCache::preserve (const QString & url) {
0319     PreserveMap::const_iterator it = preserve_map.constFind (url);
0320     if (it == preserve_map.constEnd ()) {
0321         preserve_map.insert (url, true);
0322         return true;
0323     }
0324     return false;
0325 }
0326 
0327 bool DataCache::isPreserved (const QString & url) {
0328     return preserve_map.find (url) != preserve_map.end ();
0329 }
0330 
0331 bool DataCache::unpreserve (const QString & url) {
0332     const PreserveMap::iterator it = preserve_map.find (url);
0333     if (it == preserve_map.end ())
0334         return false;
0335     preserve_map.erase (it);
0336     Q_EMIT preserveRemoved (url);
0337     return true;
0338 }
0339 
0340 //------------------------%<----------------------------------------------------
0341 
0342 static bool isPlayListMime (const QString & mime) {
0343     QString m (mime);
0344     int plugin_pos = m.indexOf ("-plugin");
0345     if (plugin_pos > 0)
0346         m.truncate (plugin_pos);
0347     QByteArray ba = m.toLatin1 ();
0348     const char * mimestr = ba.data ();
0349     qCDebug(LOG_KMPLAYER_COMMON) << "isPlayListMime " << mimestr;
0350     return mimestr && (!strcmp (mimestr, "audio/mpegurl") ||
0351             !strcmp (mimestr, "audio/x-mpegurl") ||
0352             !strncmp (mimestr, "video/x-ms", 10) ||
0353             !strncmp (mimestr, "audio/x-ms", 10) ||
0354             //!strcmp (mimestr, "video/x-ms-wmp") ||
0355             //!strcmp (mimestr, "video/x-ms-asf") ||
0356             //!strcmp (mimestr, "video/x-ms-wmv") ||
0357             //!strcmp (mimestr, "video/x-ms-wvx") ||
0358             //!strcmp (mimestr, "video/x-msvideo") ||
0359             !strcmp (mimestr, "audio/x-scpls") ||
0360             !strcmp (mimestr, "audio/x-shoutcast-stream") ||
0361             !strcmp (mimestr, "audio/x-pn-realaudio") ||
0362             !strcmp (mimestr, "audio/vnd.rn-realaudio") ||
0363             !strcmp (mimestr, "audio/m3u") ||
0364             !strcmp (mimestr, "audio/x-m3u") ||
0365             !strncmp (mimestr, "text/", 5) ||
0366             (!strncmp (mimestr, "application/", 12) &&
0367              strstr (mimestr + 12,"+xml")) ||
0368             !strncasecmp (mimestr, "application/smil", 16) ||
0369             !strncasecmp (mimestr, "application/xml", 15) ||
0370             //!strcmp (mimestr, "application/rss+xml") ||
0371             //!strcmp (mimestr, "application/atom+xml") ||
0372             !strcmp (mimestr, "image/svg+xml") ||
0373             !strcmp (mimestr, "image/vnd.rn-realpix") ||
0374             !strcmp (mimestr, "application/x-mplayer2"));
0375 }
0376 
0377 static QString mimeByContent (const QByteArray &data)
0378 {
0379     const QMimeType mimeType = QMimeDatabase().mimeTypeForData(data);
0380     if (mimeType.isValid())
0381         return mimeType.name ();
0382     return QString ();
0383 }
0384 
0385 MediaInfo::MediaInfo (Node *n, MediaManager::MediaType t)
0386  : media (nullptr), type (t), node (n), job (nullptr),
0387     preserve_wait (false), check_access (false) {
0388 }
0389 
0390 MediaInfo::~MediaInfo () {
0391     clearData ();
0392 }
0393 
0394 void MediaInfo::killWGet () {
0395     if (job) {
0396         job->kill (); // quiet, no result signal
0397         job = nullptr;
0398         memory_cache->unpreserve (url);
0399     } else if (preserve_wait) {
0400         disconnect (memory_cache, &DataCache::preserveRemoved,
0401                     this, &MediaInfo::cachePreserveRemoved);
0402         preserve_wait = false;
0403     }
0404 }
0405 
0406 /**
0407  * Gets contents from url and puts it in m_data
0408  */
0409 bool MediaInfo::wget(const QString& str, const QString& domain) {
0410     clearData ();
0411     url = str;
0412 
0413     if (MediaManager::Any == type || MediaManager::Image == type) {
0414         ImageDataMap::iterator i = image_data_map->find (str);
0415         if (i != image_data_map->end ()) {
0416             media = new ImageMedia (node, i.value ());
0417             type = MediaManager::Image;
0418             ready ();
0419             return true;
0420         }
0421     }
0422 
0423     Mrl *mrl = node->mrl ();
0424     if (mrl && (MediaManager::Any == type || MediaManager::AudioVideo == type))
0425     {
0426         if (!mrl->mimetype.isEmpty ())
0427             setMimetype (mrl->mimetype);
0428         if (mrl && (MediaManager::Any == type || MediaManager::AudioVideo == type))
0429             if (mime == "application/x-shockwave-flash" ||
0430                     mime == "application/futuresplash" ||
0431                     str.startsWith ("tv:")) {
0432                 ready ();
0433                 return true; // FIXME
0434             }
0435     }
0436 
0437     QUrl kurl = QUrl::fromUserInput(str);
0438     if (!mrl || !mrl->access_granted)
0439         for (Node *p = node->parentNode (); p; p = p->parentNode ()) {
0440             Mrl *m = p->mrl ();
0441             if (m && !m->src.isEmpty () &&
0442                   m->src != "Playlist://" &&
0443                   !KUrlAuthorized::authorizeUrlAction ("redirect", QUrl::fromUserInput(m->src), kurl)) {
0444                 qCWarning(LOG_KMPLAYER_COMMON) << "redirect access denied";
0445                 ready ();
0446                 return true;
0447             }
0448         }
0449 
0450     bool only_playlist = false;
0451     bool maybe_playlist = false;
0452     if (MediaManager::Audio == type
0453             || MediaManager::AudioVideo == type
0454             || MediaManager::Any == type) {
0455         only_playlist = true;
0456         maybe_playlist = isPlayListMime (mime);
0457     }
0458 
0459     if (kurl.isLocalFile ()) {
0460         QFile file (kurl.toLocalFile ());
0461         if (file.exists ()) {
0462             if (MediaManager::Data != type && mime.isEmpty ()) {
0463                 const QMimeType mimeTyoe = QMimeDatabase().mimeTypeForUrl (kurl);
0464                 if (mrl && mimeTyoe.isValid()) {
0465                     mrl->mimetype = mimeTyoe.name ();
0466                     setMimetype (mrl->mimetype);
0467                 }
0468                 qCDebug(LOG_KMPLAYER_COMMON) << "wget2 " << str << " " << mime;
0469             } else {
0470                 setMimetype (mime);
0471             }
0472             only_playlist = MediaManager::Audio == type ||
0473                 MediaManager::AudioVideo == type;
0474             maybe_playlist = isPlayListMime (mime); // get new mime
0475             if (file.open (QIODevice::ReadOnly)) {
0476                 if (only_playlist) {
0477                     maybe_playlist &= file.size () < 2000000;
0478                     if (maybe_playlist) {
0479                         char databuf [512];
0480                         int nr_bytes = file.read (databuf, 512);
0481                         if (nr_bytes > 3 &&
0482                                 (::isBufferBinaryData (QByteArray (databuf, nr_bytes)) ||
0483                                  !strncmp (databuf, "RIFF", 4)))
0484                             maybe_playlist = false;
0485                     }
0486                     if (!maybe_playlist) {
0487                         ready ();
0488                         return true;
0489                     }
0490                     file.reset ();
0491                 }
0492                 data = file.readAll ();
0493                 file.close ();
0494             }
0495         }
0496         ready ();
0497         return true;
0498     }
0499     QString protocol = kurl.scheme ();
0500     if (!domain.isEmpty ()) {
0501         QString get_from = protocol + "://" + kurl.host ();
0502         if (get_from != domain) {
0503             check_access = true;
0504             access_from = domain;
0505             cross_domain = get_from + "/crossdomain.xml";
0506             kurl = QUrl (cross_domain);
0507         }
0508     }
0509     if (!check_access) {
0510         if (MediaManager::Data != type &&
0511                 (memory_cache->get (str, mime, data) ||
0512                  protocol == "mms" || protocol == "rtsp" ||
0513                  protocol == "rtp" || protocol == "rtmp" ||
0514                  (only_playlist && !maybe_playlist && !mime.isEmpty ()))) {
0515             setMimetype (mime);
0516             if (MediaManager::Any == type)
0517                 type = MediaManager::AudioVideo;
0518             ready ();
0519             return true;
0520         }
0521     }
0522     if (check_access || memory_cache->preserve (str)) {
0523         //qCDebug(LOG_KMPLAYER_COMMON) << "downloading " << str;
0524         job = KIO::get (kurl, KIO::NoReload, KIO::HideProgressInfo);
0525         job->addMetaData ("PropagateHttpHeader", "true");
0526         job->addMetaData ("errorPage", "false");
0527         connect (job, &KIO::TransferJob::data,
0528                 this, &MediaInfo::slotData);
0529         connect (job, &KJob::result,
0530                 this, &MediaInfo::slotResult);
0531         if (!check_access)
0532             connect (job, QOverload<KIO::Job*, const QString&>::of(&KIO::TransferJob::mimetype),
0533                     this, &MediaInfo::slotMimetype);
0534     } else {
0535         //qCDebug(LOG_KMPLAYER_COMMON) << "download preserved " << str;
0536         connect (memory_cache, &DataCache::preserveRemoved,
0537                  this, &MediaInfo::cachePreserveRemoved);
0538         preserve_wait = true;
0539     }
0540     return false;
0541 }
0542 
0543 bool MediaInfo::readChildDoc () {
0544     QTextStream textstream (data, QIODevice::ReadOnly);
0545     QString line;
0546     NodePtr cur_elm = node;
0547     do {
0548         line = textstream.readLine ();
0549     } while (!line.isNull () && line.trimmed ().isEmpty ());
0550     if (!line.isNull ()) {
0551         bool pls_groupfound =
0552             line.startsWith ("[") && line.endsWith ("]") &&
0553             line.mid (1, line.size () - 2).trimmed () == "playlist";
0554         if ((pls_groupfound &&
0555                     cur_elm->mrl ()->mimetype.startsWith ("audio/")) ||
0556                 cur_elm->mrl ()->mimetype == QString ("audio/x-scpls")) {
0557             int nr = -1;
0558             struct Entry {
0559                 QString url, title;
0560             } * entries = nullptr;
0561             do {
0562                 line = line.trimmed ();
0563                 if (!line.isEmpty ()) {
0564                     if (line.startsWith ("[") && line.endsWith ("]")) {
0565                         if (line.mid (1, line.size () - 2).trimmed () == "playlist")
0566                             pls_groupfound = true;
0567                         else
0568                             break;
0569                         qCDebug(LOG_KMPLAYER_COMMON) << "Group found: " << line;
0570                     } else if (pls_groupfound) {
0571                         int eq_pos = line.indexOf (QChar ('='));
0572                         if (eq_pos > 0) {
0573                             if (line.toLower ().startsWith (QString ("numberofentries"))) {
0574                                 nr = line.mid (eq_pos + 1).trimmed ().toInt ();
0575                                 qCDebug(LOG_KMPLAYER_COMMON) << "numberofentries : " << nr;
0576                                 if (nr > 0 && nr < 1024)
0577                                     entries = new Entry[nr];
0578                                 else
0579                                     nr = 0;
0580                             } else if (nr > 0) {
0581                                 QString ll = line.toLower ();
0582                                 if (ll.startsWith (QString ("file"))) {
0583                                     int i = line.mid (4, eq_pos-4).toInt ();
0584                                     if (i > 0 && i <= nr)
0585                                         entries[i-1].url = line.mid (eq_pos + 1).trimmed ();
0586                                 } else if (ll.startsWith (QString ("title"))) {
0587                                     int i = line.mid (5, eq_pos-5).toInt ();
0588                                     if (i > 0 && i <= nr)
0589                                         entries[i-1].title = line.mid (eq_pos + 1).trimmed ();
0590                                 }
0591                             }
0592                         }
0593                     }
0594                 }
0595                 line = textstream.readLine ();
0596             } while (!line.isNull ());
0597             NodePtr doc = node->document ();
0598             for (int i = 0; i < nr; i++)
0599                 if (!entries[i].url.isEmpty ())
0600                     cur_elm->appendChild (new GenericURL (doc,
0601                            QUrl::fromPercentEncoding (entries[i].url.toUtf8 ()),
0602                            entries[i].title));
0603             delete [] entries;
0604         } else if (line.trimmed ().startsWith (QChar ('<'))) {
0605             readXML (cur_elm, textstream, line);
0606             //cur_elm->normalize ();
0607         } else if (line.toLower () != QString ("[reference]")) {
0608             bool extm3u = line.startsWith ("#EXTM3U");
0609             QString title;
0610             NodePtr doc = node->document ();
0611             if (extm3u)
0612                 line = textstream.readLine ();
0613             while (!line.isNull ()) {
0614              /* TODO && m_document.size () < 1024 / * support 1k entries * /);*/
0615                 QString mrl = line.trimmed ();
0616                 if (line == QString ("--stop--"))
0617                     break;
0618                 if (mrl.toLower ().startsWith (QString ("asf ")))
0619                     mrl = mrl.mid (4).trimmed ();
0620                 if (!mrl.isEmpty ()) {
0621                     if (extm3u && mrl.startsWith (QChar ('#'))) {
0622                         if (line.startsWith ("#EXTINF:"))
0623                             title = line.mid (9);
0624                         else
0625                             title = mrl.mid (1);
0626                     } else if (!line.startsWith (QChar ('#'))) {
0627                         cur_elm->appendChild (new GenericURL (doc, mrl, title));
0628                         title.truncate (0);
0629                     }
0630                 }
0631                 line = textstream.readLine ();
0632             }
0633         }
0634     }
0635     return !cur_elm->isPlayable ();
0636 }
0637 
0638 void MediaInfo::setMimetype (const QString &mt)
0639 {
0640     mime = mt;
0641 
0642     Mrl *mrl = node ? node->mrl () : nullptr;
0643     if (mrl && mrl->mimetype.isEmpty ())
0644         mrl->mimetype = mt;
0645 
0646     if (MediaManager::Any == type) {
0647         if (mimetype ().startsWith ("image/"))
0648             type = MediaManager::Image;
0649         else if (mime.startsWith ("audio/"))
0650             type = MediaManager::Audio;
0651         else
0652             type = MediaManager::AudioVideo;
0653     }
0654 }
0655 
0656 QString MediaInfo::mimetype () {
0657     if (data.size () > 0 && mime.isEmpty ())
0658         setMimetype (mimeByContent (data));
0659     return mime;
0660 }
0661 
0662 void MediaInfo::clearData () {
0663     killWGet ();
0664     if (media) {
0665         media->destroy ();
0666         media = nullptr;
0667     }
0668     url.truncate (0);
0669     mime.truncate (0);
0670     access_from.truncate (0);
0671     data.resize (0);
0672 }
0673 
0674 bool MediaInfo::downloading () const {
0675     return !!job;
0676 }
0677 
0678 void MediaInfo::create () {
0679     MediaManager *mgr = (MediaManager*)node->document()->role(RoleMediaManager);
0680     if (!media && mgr) {
0681         switch (type) {
0682         case MediaManager::Audio:
0683         case MediaManager::AudioVideo:
0684             qCDebug(LOG_KMPLAYER_COMMON) << data.size ();
0685             if (!data.size () || !readChildDoc ())
0686                 media = mgr->createAVMedia (node, data);
0687             break;
0688         case MediaManager::Image:
0689             if (data.size () && mime == "image/svg+xml") {
0690                 readChildDoc ();
0691                 if (node->firstChild () &&
0692                         id_node_svg == node->lastChild ()->id) {
0693                     media = new ImageMedia (node);
0694                     break;
0695                 }
0696             }
0697             if (data.size () &&
0698                     (!(mimetype ().startsWith ("text/") ||
0699                        mime == "image/vnd.rn-realpix") ||
0700                      !readChildDoc ()))
0701                 media = new ImageMedia (mgr, node, url, data);
0702             break;
0703         case MediaManager::Text:
0704             if (data.size ())
0705                 media = new TextMedia (mgr, node, data);
0706             break;
0707         default: // Any
0708             break;
0709         }
0710     }
0711 }
0712 
0713 void MediaInfo::ready () {
0714     if (MediaManager::Data != type) {
0715         create ();
0716         if (id_node_record_document == node->id)
0717             node->message (MsgMediaReady);
0718         else
0719             node->document()->post (node, new Posting (node, MsgMediaReady));
0720     } else {
0721         node->message (MsgMediaReady);
0722     }
0723 }
0724 
0725 static bool validDataFormat (MediaManager::MediaType type, const QByteArray &ba)
0726 {
0727     switch (type) {
0728         case MediaManager::Audio:
0729         case MediaManager::AudioVideo:
0730             return !(ba.size () > 2000000 || ba.size () < 4 ||
0731                     ::isBufferBinaryData (ba) ||
0732                      !strncmp (ba.data (), "RIFF", 4));
0733         default:
0734             return true;
0735     }
0736 }
0737 
0738 void MediaInfo::slotResult (KJob *kjob) {
0739     job = nullptr; // signal KIO::Job::result deletes itself
0740     if (check_access) {
0741         check_access = false;
0742 
0743         bool success = false;
0744         if (!kjob->error () && data.size () > 0) {
0745             QTextStream ts (data, QIODevice::ReadOnly);
0746             NodePtr doc = new Document (QString ());
0747             readXML (doc, ts, QString ());
0748 
0749             Expression *expr = evaluateExpr (
0750                     "//cross-domain-policy/allow-access-from/@domain");
0751             if (expr) {
0752                 expr->setRoot (doc);
0753                 Expression::iterator it, e = expr->end();
0754                 for (it = expr->begin(); it != e; ++it) {
0755                     QRegExp match (it->value(), Qt::CaseInsensitive, QRegExp::Wildcard);
0756                     if (match.exactMatch (access_from)) {
0757                         success = true;
0758                         break;
0759                     }
0760                 }
0761                 delete expr;
0762             }
0763             doc->document ()->dispose ();
0764         }
0765         if (success) {
0766             wget (QString (url));
0767         } else {
0768             data.resize (0);
0769             ready ();
0770         }
0771     } else {
0772         if (MediaManager::Data != type && !kjob->error ()) {
0773             if (data.size () && data.size () < 512) {
0774                 setMimetype (mimeByContent (data));
0775                 if (!validDataFormat (type, data))
0776                     data.resize (0);
0777             }
0778             memory_cache->add (url, mime, data);
0779         } else {
0780             memory_cache->unpreserve (url);
0781             if (MediaManager::Data != type)
0782                 data.resize (0);
0783         }
0784         ready ();
0785     }
0786 }
0787 
0788 void MediaInfo::cachePreserveRemoved (const QString & str) {
0789     if (str == url && !memory_cache->isPreserved (str)) {
0790         preserve_wait = false;
0791         disconnect (memory_cache, &DataCache::preserveRemoved,
0792                     this, &MediaInfo::cachePreserveRemoved);
0793         wget (str);
0794     }
0795 }
0796 
0797 void MediaInfo::slotData (KIO::Job *, const QByteArray &qb) {
0798     if (qb.size ()) {
0799         int old_size = data.size ();
0800         int newsize = old_size + qb.size ();
0801         data.resize (newsize);
0802         memcpy (data.data () + old_size, qb.constData (), qb.size ());
0803         if (!check_access && old_size < 512 && newsize >= 512) {
0804             setMimetype (mimeByContent (data));
0805             if (!validDataFormat (type, data)) {
0806                 data.resize (0);
0807                 job->kill (KJob::EmitResult);
0808                 return;
0809             }
0810         }
0811     }
0812 }
0813 
0814 void MediaInfo::slotMimetype (KIO::Job *, const QString & m) {
0815     Mrl *mrl = node->mrl ();
0816     mime = m;
0817     if (mrl)
0818         mrl->mimetype = m;
0819     switch (type) {
0820     case MediaManager::Any:
0821         //fall through
0822         break;
0823     case MediaManager::Audio:
0824     case MediaManager::AudioVideo:
0825         if (!isPlayListMime (m))
0826             job->kill (KJob::EmitResult);
0827         break;
0828     default:
0829         //TODO
0830         break;
0831     }
0832 }
0833 
0834 //------------------------%<----------------------------------------------------
0835 
0836 IProcess::IProcess (ProcessInfo *pinfo) :
0837     user (nullptr),
0838     process_info (pinfo),
0839     m_state (NotRunning) {}
0840 
0841 AudioVideoMedia::AudioVideoMedia (MediaManager *manager, Node *node)
0842  : MediaObject (manager, node),
0843    process (nullptr),
0844    m_viewer (nullptr),
0845    request (ask_nothing) {
0846     qCDebug(LOG_KMPLAYER_COMMON) << "AudioVideoMedia::AudioVideoMedia" << endl;
0847 }
0848 
0849 AudioVideoMedia::~AudioVideoMedia () {
0850     stop ();
0851     // delete m_process;
0852     if (m_viewer) { //nicer with QObject destroy signal, but preventing unmap on destruction
0853         View *view = m_manager->player ()->viewWidget ();
0854         if (view)
0855             view->viewArea ()->destroyVideoWidget (m_viewer);
0856     }
0857     if (process) {
0858         request = ask_nothing;
0859         delete process;
0860     }
0861     qCDebug(LOG_KMPLAYER_COMMON) << "AudioVideoMedia::~AudioVideoMedia";
0862 }
0863 
0864 bool AudioVideoMedia::play () {
0865     qCDebug(LOG_KMPLAYER_COMMON) << process;
0866     if (process) {
0867         qCDebug(LOG_KMPLAYER_COMMON) << process->state ();
0868         if (process->state () > IProcess::Ready) {
0869             qCCritical(LOG_KMPLAYER_COMMON) << "already playing" << endl;
0870             return true;
0871         }
0872         if (process->state () != IProcess::Ready) {
0873             request = ask_play;
0874             return true; // FIXME add Launching state
0875         }
0876         m_manager->playAudioVideo (this);
0877         return true;
0878     }
0879     return false;
0880 }
0881 
0882 bool AudioVideoMedia::grabPicture (const QString &file, int frame) {
0883     if (process) {
0884         qCDebug(LOG_KMPLAYER_COMMON) << "AudioVideoMedia::grab " << file << endl;
0885         m_grab_file = file;
0886         m_frame = frame;
0887         if (process->state () < IProcess::Ready) {
0888             request = ask_grab;
0889             return true; // FIXME add Launching state
0890         }
0891         m_manager->grabPicture (this);
0892         return true;
0893     }
0894     return false;
0895 }
0896 
0897 void AudioVideoMedia::stop () {
0898     if (ask_delete != request)
0899         request = ask_stop;
0900     if (process)
0901         process->stop ();
0902     if (m_manager->player ()->view () && m_viewer)
0903         m_viewer->unmap ();
0904 }
0905 
0906 void AudioVideoMedia::pause () {
0907     if (process) {
0908         if (process->state () > IProcess::Ready) {
0909             request = ask_nothing;
0910             process->pause ();
0911         } else {
0912             request = ask_pause;
0913         }
0914     }
0915 }
0916 
0917 void AudioVideoMedia::unpause () {
0918     if (process) {
0919         if (request == ask_pause) {
0920             request = ask_nothing;
0921         } else {
0922             process->unpause ();
0923         }
0924     }
0925 }
0926 
0927 void AudioVideoMedia::destroy () {
0928     if (m_manager->player ()->view () && m_viewer)
0929         m_viewer->unmap ();
0930     if (!process || IProcess::Ready >= process->state ()) {
0931         delete this;
0932     } else {
0933         stop ();
0934         request = ask_delete;
0935     }
0936 }
0937 
0938 void AudioVideoMedia::starting (IProcess*) {
0939     request = AudioVideoMedia::ask_nothing;
0940 }
0941 
0942 void AudioVideoMedia::stateChange (IProcess *,
0943                                    IProcess::State os, IProcess::State ns) {
0944     m_manager->stateChange (this, os, ns);
0945 }
0946 
0947 void AudioVideoMedia::processDestroyed (IProcess *p) {
0948     m_manager->processDestroyed (p);
0949     process = nullptr;
0950     if (ask_delete == request)
0951         delete this;
0952 }
0953 
0954 IViewer *AudioVideoMedia::viewer () {
0955     return m_viewer;
0956 }
0957 
0958 Mrl *AudioVideoMedia::getMrl () {
0959     return mrl ();
0960 }
0961 
0962 //------------------------%<----------------------------------------------------
0963 
0964 #ifdef KMPLAYER_WITH_CAIRO
0965 # include <cairo.h>
0966 #endif
0967 
0968 ImageData::ImageData( const QString & img)
0969  : width (0),
0970    height (0),
0971    flags (0),
0972    has_alpha (false),
0973    image (nullptr),
0974 #ifdef KMPLAYER_WITH_CAIRO
0975    surface (nullptr),
0976 #endif
0977    url (img) {
0978     //if (img.isEmpty ())
0979     //    //qCDebug(LOG_KMPLAYER_COMMON) << "New ImageData for " << this << endl;
0980     //else
0981     //    //qCDebug(LOG_KMPLAYER_COMMON) << "New ImageData for " << img << endl;
0982 }
0983 
0984 ImageData::~ImageData() {
0985     if (!url.isEmpty ())
0986         image_data_map->remove (url);
0987 #ifdef KMPLAYER_WITH_CAIRO
0988     if (surface)
0989         cairo_surface_destroy (surface);
0990 #endif
0991     delete image;
0992 }
0993 
0994 void ImageData::setImage (QImage *img) {
0995     if (image != img) {
0996         delete image;
0997 #ifdef KMPLAYER_WITH_CAIRO
0998         if (surface) {
0999             cairo_surface_destroy (surface);
1000             surface = nullptr;
1001         }
1002 #endif
1003         image = img;
1004         if (img) {
1005             width = img->width ();
1006             height = img->height ();
1007             has_alpha = img->hasAlphaChannel ();
1008         } else {
1009             width = height = 0;
1010         }
1011     }
1012 }
1013 
1014 ImageMedia::ImageMedia (MediaManager *manager, Node *node,
1015         const QString &url, const QByteArray &ba)
1016  : MediaObject (manager, node), data (ba), buffer (nullptr),
1017    img_movie (nullptr),
1018    svg_renderer (nullptr),
1019    update_render (false),
1020    paused (false) {
1021     setupImage (url);
1022 }
1023 
1024 ImageMedia::ImageMedia (Node *node, ImageDataPtr id)
1025  : MediaObject ((MediaManager *)node->document()->role (RoleMediaManager),
1026          node),
1027    buffer (nullptr),
1028    img_movie (nullptr),
1029    svg_renderer (nullptr),
1030    update_render (false) {
1031     if (!id) {
1032         Node *c = findChildWithId (node, id_node_svg);
1033         if (c) {
1034             svg_renderer = new QSvgRenderer (c->outerXML().toUtf8 ());
1035             if (svg_renderer->isValid ()) {
1036                 cached_img = new ImageData (QString ());
1037                 cached_img->flags = ImageData::ImageScalable;
1038                 if (svg_renderer->animated())
1039                     connect(svg_renderer, &QSvgRenderer::repaintNeeded,
1040                             this, &ImageMedia::svgUpdated);
1041             } else {
1042                 delete svg_renderer;
1043                 svg_renderer = nullptr;
1044             }
1045         }
1046     } else {
1047         cached_img = id;
1048     }
1049 }
1050 
1051 ImageMedia::~ImageMedia () {
1052     delete img_movie;
1053     delete svg_renderer;
1054     delete buffer;
1055 }
1056 
1057 bool ImageMedia::play () {
1058     if (!img_movie)
1059         return false;
1060     if (img_movie->state () == QMovie::Paused)
1061         img_movie->setPaused (false);
1062     else if (img_movie->state () != QMovie::Running)
1063         img_movie->start ();
1064     return true;
1065 }
1066 
1067 void ImageMedia::stop () {
1068     pause ();
1069 }
1070 
1071 void ImageMedia::pause () {
1072     if (!paused && svg_renderer && svg_renderer->animated())
1073         disconnect(svg_renderer, &QSvgRenderer::repaintNeeded,
1074                 this, &ImageMedia::svgUpdated);
1075     if (img_movie && img_movie->state () != QMovie::Paused)
1076         img_movie->setPaused (true);
1077     paused = true;
1078 }
1079 
1080 void ImageMedia::unpause () {
1081     if (paused && svg_renderer && svg_renderer->animated())
1082         connect(svg_renderer, &QSvgRenderer::repaintNeeded,
1083                 this, &ImageMedia::svgUpdated);
1084     if (img_movie && QMovie::Paused == img_movie->state ())
1085         img_movie->setPaused (false);
1086     paused = false;
1087 }
1088 
1089 void ImageMedia::setupImage (const QString &url) {
1090     if (isEmpty () && data.size ()) {
1091         QImage *pix = new QImage;
1092         if (pix->loadFromData((data))) {
1093             cached_img = ImageDataPtr (new ImageData (url));
1094             cached_img->setImage (pix);
1095         } else {
1096             delete pix;
1097         }
1098     }
1099     if (!isEmpty ()) {
1100         buffer = new QBuffer (&data);
1101         img_movie = new QMovie (buffer);
1102         //qCDebug(LOG_KMPLAYER_COMMON) << img_movie->frameCount ();
1103         if (img_movie->frameCount () > 1) {
1104             cached_img->flags |= (short)ImageData::ImagePixmap | ImageData::ImageAnimated;
1105             connect (img_movie, &QMovie::updated,
1106                     this, &ImageMedia::movieUpdated);
1107             connect (img_movie, &QMovie::stateChanged,
1108                     this, &ImageMedia::movieStatus);
1109             connect (img_movie, &QMovie::resized,
1110                     this, &ImageMedia::movieResize);
1111         } else {
1112             delete img_movie;
1113             img_movie = nullptr;
1114             delete buffer;
1115             buffer = nullptr;
1116             frame_nr = 0;
1117             cached_img->flags |= (short)ImageData::ImagePixmap;
1118             image_data_map->insert (url, ImageDataPtrW (cached_img));
1119         }
1120     }
1121 }
1122 
1123 void ImageMedia::render (const ISize &sz) {
1124     if (svg_renderer && update_render) {
1125         delete svg_renderer;
1126         svg_renderer = nullptr;
1127         Node *c = findChildWithId (m_node, id_node_svg);
1128         if (c) {
1129             QSvgRenderer *r = new QSvgRenderer (c->outerXML().toUtf8 ());
1130             if (r->isValid ()) {
1131                 cached_img->setImage (nullptr);
1132                 svg_renderer = r;
1133             } else {
1134                 delete r;
1135             }
1136         }
1137         update_render = false;
1138     }
1139     if (svg_renderer &&
1140            (cached_img->width != sz.width || cached_img->height != sz.height)) {
1141         QImage *img = new QImage (sz.width, sz.height,
1142                 QImage::Format_ARGB32_Premultiplied);
1143         img->fill (0x0);
1144         QPainter paint (img);
1145         paint.setViewport (QRect (0, 0, sz.width, sz.height));
1146         svg_renderer->render (&paint);
1147         cached_img->setImage (img);
1148     }
1149 }
1150 
1151 void ImageMedia::updateRender () {
1152     update_render = true;
1153     if (m_node)
1154         m_node->document()->post(m_node, new Posting (m_node, MsgMediaUpdated));
1155 }
1156 
1157 void ImageMedia::sizes (SSize &size) {
1158     if (svg_renderer) {
1159         QSize s = svg_renderer->defaultSize ();
1160         size.width = s.width ();
1161         size.height = s.height ();
1162     } else if (cached_img) {
1163         size.width = cached_img->width;
1164         size.height = cached_img->height;
1165     } else {
1166         size.width = 0;
1167         size.height = 0;
1168     }
1169 }
1170 
1171 bool ImageMedia::isEmpty () const {
1172     return !cached_img || (!svg_renderer && cached_img->isEmpty ());
1173 }
1174 
1175 void ImageMedia::svgUpdated() {
1176     cached_img->setImage (nullptr);
1177     if (m_node)
1178         m_node->document ()->post (m_node, new Posting (m_node, MsgMediaUpdated));
1179 }
1180 
1181 void ImageMedia::movieResize (const QSize &) {
1182     //qCDebug(LOG_KMPLAYER_COMMON) << "movieResize" << endl;
1183     if (m_node)
1184         m_node->document ()->post (m_node, new Posting (m_node, MsgMediaUpdated));
1185 }
1186 
1187 void ImageMedia::movieUpdated (const QRect &) {
1188     if (frame_nr++) {
1189         Q_ASSERT (cached_img && isEmpty ());
1190         QImage *img = new QImage;
1191         *img = img_movie->currentImage ();
1192         cached_img->setImage (img);
1193         cached_img->flags = (int)(ImageData::ImagePixmap | ImageData::ImageAnimated); //TODO
1194         if (m_node)
1195             m_node->document ()->post (m_node, new Posting (m_node, MsgMediaUpdated));
1196     }
1197 }
1198 
1199 void ImageMedia::movieStatus (QMovie::MovieState status) {
1200     if (QMovie::NotRunning == status && m_node)
1201         m_node->document ()->post (m_node, new Posting (m_node, MsgMediaFinished));
1202 }
1203 
1204 //------------------------%<----------------------------------------------------
1205 
1206 static int default_font_size = -1;
1207 
1208 TextMedia::TextMedia (MediaManager *manager, Node *node, const QByteArray &ba)
1209  : MediaObject (manager, node) {
1210     QByteArray data (ba);
1211     if (!data [data.size () - 1])
1212         data.resize (data.size () - 1); // strip zero terminate char
1213     QTextStream ts (data, QIODevice::ReadOnly);
1214     QString val = convertNode <Element> (node)->getAttribute ("charset");
1215     if (!val.isEmpty ()) {
1216         QTextCodec *codec = QTextCodec::codecForName (val.toLatin1 ());
1217         if (codec)
1218             ts.setCodec (codec);
1219     }
1220     if (node->mrl() && node->mrl()->mimetype == "text/html") {
1221         Document *doc = new Document (QString ());
1222         NodePtr store = doc;
1223         readXML (doc, ts, QString ());
1224         text = doc->innerText ();
1225         doc->dispose ();
1226     } else {
1227         text = ts.readAll ();
1228     }
1229 }
1230 
1231 TextMedia::~TextMedia () {
1232 }
1233 
1234 bool TextMedia::play () {
1235     return !text.isEmpty ();
1236 }
1237 
1238 int TextMedia::defaultFontSize () {
1239     if (default_font_size < 0)
1240         default_font_size = QApplication::font ().pointSize ();
1241     return default_font_size;
1242 }
1243 
1244 #include "moc_mediaobject.cpp"