File indexing completed on 2024-04-21 15:38:24

0001 /**
0002   This file belong to the KMPlayer project, a movie player plugin for Konqueror
0003   Copyright (C) 2007  Koos Vriezen <koos.vriezen@gmail.com>
0004 
0005   This library is free software; you can redistribute it and/or
0006   modify it under the terms of the GNU Lesser General Public
0007   License as published by the Free Software Foundation; either
0008   version 2 of the License, or (at your option) any later version.
0009 
0010   This library is distributed in the hope that it will be useful,
0011   but WITHOUT ANY WARRANTY; without even the implied warranty of
0012   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0013   Lesser General Public License for more details.
0014 
0015   You should have received a copy of the GNU Lesser General Public
0016   License along with this library; if not, write to the Free Software
0017   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
0018 **/
0019 
0020 #include <qtextstream.h>
0021 #include <qapplication.h>
0022 #include <qmovie.h>
0023 #include <QBuffer>
0024 #include <QPainter>
0025 #include <QtSvg/QSvgRenderer>
0026 #include <qimage.h>
0027 #include <qfile.h>
0028 #include <qurl.h>
0029 #include <qtextcodec.h>
0030 #include <qtextstream.h>
0031 
0032 #include <kdebug.h>
0033 #include <kmimetype.h>
0034 #include <klocalizedstring.h>
0035 #include <kio/job.h>
0036 #include <kio/jobclasses.h>
0037 #include <kmimetype.h>
0038 #include <kurlauthorized.h>
0039 
0040 #include "mediaobject.h"
0041 #include "kmplayerprocess.h"
0042 #include "kmplayerview.h"
0043 #include "expression.h"
0044 #include "viewarea.h"
0045 #include "kmplayerpartbase.h"
0046 
0047 using namespace KMPlayer;
0048 
0049 namespace {
0050 
0051     typedef QMap <QString, ImageDataPtrW> ImageDataMap;
0052 
0053     static DataCache *memory_cache;
0054     static ImageDataMap *image_data_map;
0055 
0056     struct GlobalMediaData : public GlobalShared<GlobalMediaData> {
0057         GlobalMediaData (GlobalMediaData **gb)
0058          : GlobalShared<GlobalMediaData> (gb) {
0059             memory_cache = new DataCache;
0060             image_data_map = new ImageDataMap;
0061         }
0062         ~GlobalMediaData ();
0063     };
0064 
0065     static GlobalMediaData *global_media;
0066 
0067     GlobalMediaData::~GlobalMediaData () {
0068         delete memory_cache;
0069         delete image_data_map;
0070         global_media = NULL;
0071     }
0072 }
0073 
0074 //------------------------%<----------------------------------------------------
0075 
0076 MediaManager::MediaManager (PartBase *player) : m_player (player) {
0077     if (!global_media)
0078         (void) new GlobalMediaData (&global_media);
0079     else
0080         global_media->ref ();
0081 
0082     m_process_infos ["mplayer"] = new MPlayerProcessInfo (this);
0083     m_process_infos ["phonon"] = new PhononProcessInfo (this);
0084     //XineProcessInfo *xpi = new XineProcessInfo (this);
0085     //m_process_infos ["xine"] = xpi;
0086     //m_process_infos ["gstreamer"] = new GStreamer (this, m_settings);, i18n ("&GStreamer")
0087 #ifdef KMPLAYER_WITH_NPP
0088     m_process_infos ["npp"] = new NppProcessInfo (this);
0089 #endif
0090     m_record_infos ["mencoder"] = new MEncoderProcessInfo (this);
0091     m_record_infos ["mplayerdumpstream"] = new MPlayerDumpProcessInfo (this);
0092     m_record_infos ["ffmpeg"] = new FFMpegProcessInfo (this);
0093     //m_record_infos ["xine"] = xpi;
0094 }
0095 
0096 MediaManager::~MediaManager () {
0097     for (ProcessList::iterator i = m_processes.begin ();
0098             i != m_processes.end ();
0099             i = m_processes.begin () /*~Process removes itself from this list*/)
0100     {
0101         kDebug() << "~MediaManager " << *i << endl;
0102         delete *i;
0103     }
0104     for (ProcessList::iterator i = m_recorders.begin ();
0105             i != m_recorders.end ();
0106             i = m_recorders.begin ())
0107     {
0108         kDebug() << "~MediaManager " << *i << endl;
0109         delete *i;
0110     }
0111     const ProcessInfoMap::iterator ie = m_process_infos.end ();
0112     for (ProcessInfoMap::iterator i = m_process_infos.begin (); i != ie; ++i)
0113         if (!m_record_infos.contains (i.key ()))
0114             delete i.value ();
0115 
0116     const ProcessInfoMap::iterator rie = m_record_infos.end ();
0117     for (ProcessInfoMap::iterator i = m_record_infos.begin (); i != rie; ++i)
0118         delete i.value ();
0119 
0120     if (m_media_objects.size ()) {
0121         kError () << "~MediaManager media list not empty " << m_media_objects.size () << endl;
0122         // bug elsewere, but don't crash
0123         const MediaList::iterator me = m_media_objects.end ();
0124         for (MediaList::iterator i = m_media_objects.begin (); i != me; ) {
0125             if (*i && (*i)->mrl () &&
0126                     (*i)->mrl ()->document ()->active ()) {
0127                 (*i)->mrl ()->document ()->deactivate ();
0128                 i = m_media_objects.begin ();
0129             } else {
0130                 ++i;
0131             }
0132         }
0133         if (m_media_objects.size ())
0134             kError () << "~MediaManager media list still not empty" << m_media_objects.size () << endl;
0135     }
0136     global_media->unref ();
0137 }
0138 
0139 MediaObject *MediaManager::createAVMedia (Node *node, const QByteArray &) {
0140     RecordDocument *rec = id_node_record_document == node->id
0141         ? convertNode <RecordDocument> (node)
0142         : NULL;
0143     if (!rec && !m_player->source()->authoriseUrl (
0144                 node->mrl()->absolutePath ()))
0145         return NULL;
0146 
0147     AudioVideoMedia *av = new AudioVideoMedia (this, node);
0148     if (rec) {
0149         av->process = m_record_infos[rec->recorder]->create (m_player, av);
0150         m_recorders.push_back (av->process);
0151         kDebug() << "Adding recorder " << endl;
0152     } else {
0153         av->process = m_process_infos[m_player->processName (
0154                 av->mrl ())]->create (m_player, av);
0155         m_processes.push_back (av->process);
0156     }
0157     av->process->user = av;
0158     av->setViewer (!rec
0159         ? m_player->viewWidget ()->viewArea ()->createVideoWidget ()
0160         : NULL);
0161 
0162     if (av->process->state () <= IProcess::Ready)
0163         av->process->ready ();
0164     return av;
0165 }
0166 
0167 static const QString statemap [] = {
0168     i18n ("Not Running"), i18n ("Ready"), i18n ("Buffering"), i18n ("Playing"),  i18n ("Paused")
0169 };
0170 
0171 void MediaManager::stateChange (AudioVideoMedia *media,
0172         IProcess::State olds, IProcess::State news) {
0173     //p->viewer()->view()->controlPanel()->setPlaying(news > Process::Ready);
0174     Mrl *mrl = media->mrl ();
0175     kDebug () << "processState " << media->process->process_info->name << " "
0176         << statemap[olds] << " -> " << statemap[news];
0177 
0178     if (!mrl) { // document dispose
0179         if (IProcess::Ready < news)
0180             media->process->quit ();
0181         else
0182             delete media;
0183         return;
0184     }
0185 
0186     if (!m_player->view ()) // part destruction
0187         return;
0188 
0189     bool is_rec = id_node_record_document == mrl->id;
0190     m_player->updateStatus (i18n ("Player %1 %2",
0191                 media->process->process_info->name, statemap[news]));
0192     if (IProcess::Playing == news) {
0193         if (Element::state_deferred == mrl->state)
0194             mrl->undefer ();
0195         bool has_video = !is_rec;
0196         if (is_rec && m_recorders.contains(media->process))
0197             m_player->recorderPlaying ();
0198         if (has_video) {
0199             if (m_player->view ()) {
0200                 if (media->viewer ()) {
0201                     media->viewer ()->setAspect (mrl->aspect);
0202                     media->viewer ()->map ();
0203                 }
0204                 if (Mrl::SingleMode == mrl->view_mode)
0205                     m_player->viewWidget ()->viewArea ()->resizeEvent (NULL);
0206             }
0207         }
0208     } else if (IProcess::NotRunning == news) {
0209         if (AudioVideoMedia::ask_delete == media->request) {
0210             delete media;
0211         } else if (mrl->unfinished ()) {
0212             mrl->document ()->post (mrl, new Posting (mrl, MsgMediaFinished));
0213         }
0214     } else if (IProcess::Ready == news) {
0215         if (AudioVideoMedia::ask_play == media->request) {
0216             playAudioVideo (media);
0217         } else if (AudioVideoMedia::ask_grab == media->request) {
0218             grabPicture (media);
0219         } else {
0220             if (!is_rec && Mrl::SingleMode == mrl->view_mode) {
0221                 ProcessList::ConstIterator i, e = m_processes.constEnd ();
0222                 for (i = m_processes.constBegin(); i != e; ++i)
0223                     if (*i != media->process &&
0224                             (*i)->state () == IProcess::Ready)
0225                         (*i)->play (); // delayed playing
0226                 e = m_recorders.constEnd ();
0227                 for (i = m_recorders.constBegin (); i != e; ++i)
0228                     if (*i != media->process &&
0229                             (*i)->state () == IProcess::Ready)
0230                         (*i)->play (); // delayed recording
0231             }
0232             if (AudioVideoMedia::ask_delete == media->request) {
0233                 delete media;
0234             } else if (olds > IProcess::Ready) {
0235                 if (is_rec)
0236                     mrl->message (MsgMediaFinished, NULL); // FIXME
0237                 else
0238                     mrl->document()->post(mrl, new Posting (mrl, MsgMediaFinished));
0239             }
0240         }
0241     } else if (IProcess::Buffering == news) {
0242         if (AudioVideoMedia::ask_pause == media->request) {
0243             media->pause ();
0244         } else if (mrl->view_mode != Mrl::SingleMode) {
0245             mrl->defer (); // paused the SMIL
0246         }
0247     }
0248 }
0249 
0250 void MediaManager::playAudioVideo (AudioVideoMedia *media) {
0251     Mrl *mrl = media->mrl ();
0252     media->request = AudioVideoMedia::ask_nothing;
0253     if (!mrl ||!m_player->view ())
0254         return;
0255     if (Mrl::SingleMode == mrl->view_mode) {
0256         ProcessList::ConstIterator i, e = m_processes.constEnd ();
0257         for (i = m_processes.constBegin(); i != e; ++i)
0258             if (*i != media->process && (*i)->state () > IProcess::Ready)
0259                 return; // delay, avoiding two overlaping widgets
0260     }
0261     media->process->play ();
0262 }
0263 
0264 void MediaManager::grabPicture (AudioVideoMedia *media) {
0265     Mrl *mrl = media->mrl ();
0266     media->request = AudioVideoMedia::ask_nothing;
0267     if (!mrl)
0268         return;
0269     media->process->grabPicture (media->m_grab_file, media->m_frame);
0270 }
0271 
0272 void MediaManager::processDestroyed (IProcess *process) {
0273     kDebug() << "processDestroyed " << process << endl;
0274     m_processes.removeAll (process);
0275     m_recorders.removeAll (process);
0276 }
0277 
0278 //------------------------%<----------------------------------------------------
0279 
0280 MediaObject::MediaObject (MediaManager *manager, Node *node)
0281  : m_manager (manager), m_node (node) {
0282     manager->medias ().push_back (this);
0283 }
0284 
0285 MediaObject::~MediaObject () {
0286     m_manager->medias ().removeAll (this);
0287 }
0288 
0289 KDE_NO_EXPORT void MediaObject::destroy () {
0290     delete this;
0291 }
0292 
0293 Mrl *MediaObject::mrl () {
0294     return m_node ? m_node->mrl () : NULL;
0295 }
0296 
0297 //------------------------%<----------------------------------------------------
0298 
0299 void DataCache::add (const QString & url, const QString &mime, const QByteArray & data) {
0300     QByteArray bytes;
0301     bytes = data;
0302     cache_map.insert (url, qMakePair (mime, bytes));
0303     preserve_map.remove (url);
0304     emit preserveRemoved (url);
0305 }
0306 
0307 bool DataCache::get (const QString & url, QString &mime, QByteArray & data) {
0308     DataMap::const_iterator it = cache_map.constFind (url);
0309     if (it != cache_map.constEnd ()) {
0310         mime = it.value ().first;
0311         data = it.value ().second;
0312         return true;
0313     }
0314     return false;
0315 }
0316 
0317 bool DataCache::preserve (const QString & url) {
0318     PreserveMap::const_iterator it = preserve_map.constFind (url);
0319     if (it == preserve_map.constEnd ()) {
0320         preserve_map.insert (url, true);
0321         return true;
0322     }
0323     return false;
0324 }
0325 
0326 bool DataCache::isPreserved (const QString & url) {
0327     return preserve_map.find (url) != preserve_map.end ();
0328 }
0329 
0330 bool DataCache::unpreserve (const QString & url) {
0331     const PreserveMap::iterator it = preserve_map.find (url);
0332     if (it == preserve_map.end ())
0333         return false;
0334     preserve_map.erase (it);
0335     emit preserveRemoved (url);
0336     return true;
0337 }
0338 
0339 //------------------------%<----------------------------------------------------
0340 
0341 static bool isPlayListMime (const QString & mime) {
0342     QString m (mime);
0343     int plugin_pos = m.indexOf ("-plugin");
0344     if (plugin_pos > 0)
0345         m.truncate (plugin_pos);
0346     QByteArray ba = m.toAscii ();
0347     const char * mimestr = ba.data ();
0348     kDebug() << "isPlayListMime " << mimestr;
0349     return mimestr && (!strcmp (mimestr, "audio/mpegurl") ||
0350             !strcmp (mimestr, "audio/x-mpegurl") ||
0351             !strncmp (mimestr, "video/x-ms", 10) ||
0352             !strncmp (mimestr, "audio/x-ms", 10) ||
0353             //!strcmp (mimestr, "video/x-ms-wmp") ||
0354             //!strcmp (mimestr, "video/x-ms-asf") ||
0355             //!strcmp (mimestr, "video/x-ms-wmv") ||
0356             //!strcmp (mimestr, "video/x-ms-wvx") ||
0357             //!strcmp (mimestr, "video/x-msvideo") ||
0358             !strcmp (mimestr, "audio/x-scpls") ||
0359             !strcmp (mimestr, "audio/x-shoutcast-stream") ||
0360             !strcmp (mimestr, "audio/x-pn-realaudio") ||
0361             !strcmp (mimestr, "audio/vnd.rn-realaudio") ||
0362             !strcmp (mimestr, "audio/m3u") ||
0363             !strcmp (mimestr, "audio/x-m3u") ||
0364             !strncmp (mimestr, "text/", 5) ||
0365             (!strncmp (mimestr, "application/", 12) &&
0366              strstr (mimestr + 12,"+xml")) ||
0367             !strncasecmp (mimestr, "application/smil", 16) ||
0368             !strncasecmp (mimestr, "application/xml", 15) ||
0369             //!strcmp (mimestr, "application/rss+xml") ||
0370             //!strcmp (mimestr, "application/atom+xml") ||
0371             !strcmp (mimestr, "image/svg+xml") ||
0372             !strcmp (mimestr, "image/vnd.rn-realpix") ||
0373             !strcmp (mimestr, "application/x-mplayer2"));
0374 }
0375 
0376 static QString mimeByContent (const QByteArray &data)
0377 {
0378     int accuraty;
0379     KMimeType::Ptr mimep = KMimeType::findByContent (data, &accuraty);
0380     if (mimep)
0381         return mimep->name ();
0382     return QString ();
0383 }
0384 
0385 MediaInfo::MediaInfo (Node *n, MediaManager::MediaType t)
0386  : media (NULL), type (t), node (n), job (NULL),
0387     preserve_wait (false), check_access (false) {
0388 }
0389 
0390 MediaInfo::~MediaInfo () {
0391     clearData ();
0392 }
0393 
0394 KDE_NO_EXPORT void MediaInfo::killWGet () {
0395     if (job) {
0396         job->kill (); // quiet, no result signal
0397         job = 0L;
0398         memory_cache->unpreserve (url);
0399     } else if (preserve_wait) {
0400         disconnect (memory_cache, SIGNAL (preserveRemoved (const QString &)),
0401                     this, SLOT (cachePreserveRemoved (const QString &)));
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     KUrl kurl (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", m->src, kurl)) {
0444                 kWarning () << "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.path ());
0461         if (file.exists ()) {
0462             if (MediaManager::Data != type && mime.isEmpty ()) {
0463                 KMimeType::Ptr mimeptr = KMimeType::findByUrl (kurl);
0464                 if (mrl && mimeptr) {
0465                     mrl->mimetype = mimeptr->name ();
0466                     setMimetype (mrl->mimetype);
0467                 }
0468                 kDebug () << "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                                 (KMimeType::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.protocol ();
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 = KUrl (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         //kDebug () << "downloading " << str;
0524         job = KIO::get (kurl, KIO::NoReload, KIO::HideProgressInfo);
0525         job->addMetaData ("PropagateHttpHeader", "true");
0526         job->addMetaData ("errorPage", "false");
0527         connect (job, SIGNAL (data (KIO::Job *, const QByteArray &)),
0528                 this, SLOT (slotData (KIO::Job *, const QByteArray &)));
0529         connect (job, SIGNAL (result (KJob *)),
0530                 this, SLOT (slotResult (KJob *)));
0531         if (!check_access)
0532             connect (job, SIGNAL (mimetype (KIO::Job *, const QString &)),
0533                     this, SLOT (slotMimetype (KIO::Job *, const QString &)));
0534     } else {
0535         //kDebug () << "download preserved " << str;
0536         connect (memory_cache, SIGNAL (preserveRemoved (const QString &)),
0537                  this, SLOT (cachePreserveRemoved (const QString &)));
0538         preserve_wait = true;
0539     }
0540     return false;
0541 }
0542 
0543 KDE_NO_EXPORT 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 = 0L;
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                         kDebug () << "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                                 kDebug () << "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 () : NULL;
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 KDE_NO_EXPORT QString MediaInfo::mimetype () {
0657     if (data.size () > 0 && mime.isEmpty ())
0658         setMimetype (mimeByContent (data));
0659     return mime;
0660 }
0661 
0662 KDE_NO_EXPORT void MediaInfo::clearData () {
0663     killWGet ();
0664     if (media) {
0665         media->destroy ();
0666         media = NULL;
0667     }
0668     url.truncate (0);
0669     mime.truncate (0);
0670     access_from.truncate (0);
0671     data.resize (0);
0672 }
0673 
0674 KDE_NO_EXPORT 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             kDebug() << 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 KDE_NO_EXPORT 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                     KMimeType::isBufferBinaryData (ba) ||
0732                      !strncmp (ba.data (), "RIFF", 4));
0733         default:
0734             return true;
0735     }
0736 }
0737 
0738 KDE_NO_EXPORT void MediaInfo::slotResult (KJob *kjob) {
0739     job = 0L; // 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 KDE_NO_EXPORT void MediaInfo::cachePreserveRemoved (const QString & str) {
0789     if (str == url && !memory_cache->isPreserved (str)) {
0790         preserve_wait = false;
0791         disconnect (memory_cache, SIGNAL (preserveRemoved (const QString &)),
0792                     this, SLOT (cachePreserveRemoved (const QString &)));
0793         wget (str);
0794     }
0795 }
0796 
0797 KDE_NO_EXPORT 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 KDE_NO_EXPORT 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 (NULL),
0838     process_info (pinfo),
0839     m_state (NotRunning) {}
0840 
0841 AudioVideoMedia::AudioVideoMedia (MediaManager *manager, Node *node)
0842  : MediaObject (manager, node),
0843    process (NULL),
0844    m_viewer (NULL),
0845    request (ask_nothing) {
0846     kDebug() << "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     kDebug() << "AudioVideoMedia::~AudioVideoMedia";
0862 }
0863 
0864 bool AudioVideoMedia::play () {
0865     kDebug() << process;
0866     if (process) {
0867         kDebug() << process->state ();
0868         if (process->state () > IProcess::Ready) {
0869             kError() << "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         kDebug() << "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 = NULL;
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 (0L),
0974 #ifdef KMPLAYER_WITH_CAIRO
0975    surface (NULL),
0976 #endif
0977    url (img) {
0978     //if (img.isEmpty ())
0979     //    //kDebug() << "New ImageData for " << this << endl;
0980     //else
0981     //    //kDebug() << "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 = NULL;
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 (NULL),
1017    img_movie (NULL),
1018    svg_renderer (NULL),
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 (NULL),
1028    img_movie (NULL),
1029    svg_renderer (NULL),
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, SIGNAL(repaintNeeded()),
1040                             this, SLOT(svgUpdated()));
1041             } else {
1042                 delete svg_renderer;
1043                 svg_renderer = NULL;
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 KDE_NO_EXPORT 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 KDE_NO_EXPORT void ImageMedia::stop () {
1068     pause ();
1069 }
1070 
1071 void ImageMedia::pause () {
1072     if (!paused && svg_renderer && svg_renderer->animated())
1073         disconnect(svg_renderer, SIGNAL(repaintNeeded()),
1074                 this, SLOT(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, SIGNAL(repaintNeeded()),
1083                 this, SLOT(svgUpdated()));
1084     if (img_movie && QMovie::Paused == img_movie->state ())
1085         img_movie->setPaused (false);
1086     paused = false;
1087 }
1088 
1089 KDE_NO_EXPORT 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         //kDebug() << img_movie->frameCount ();
1103         if (img_movie->frameCount () > 1) {
1104             cached_img->flags |= (short)ImageData::ImagePixmap | ImageData::ImageAnimated;
1105             connect (img_movie, SIGNAL (updated (const QRect &)),
1106                     this, SLOT (movieUpdated (const QRect &)));
1107             connect (img_movie, SIGNAL (stateChanged (QMovie::MovieState)),
1108                     this, SLOT (movieStatus (QMovie::MovieState)));
1109             connect (img_movie, SIGNAL (resized (const QSize &)),
1110                     this, SLOT (movieResize (const QSize &)));
1111         } else {
1112             delete img_movie;
1113             img_movie = 0L;
1114             delete buffer;
1115             buffer = 0L;
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 KDE_NO_EXPORT void ImageMedia::render (const ISize &sz) {
1124     if (svg_renderer && update_render) {
1125         delete svg_renderer;
1126         svg_renderer = NULL;
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 (NULL);
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 KDE_NO_EXPORT 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 KDE_NO_EXPORT 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 KDE_NO_EXPORT void ImageMedia::svgUpdated() {
1176     cached_img->setImage (NULL);
1177     if (m_node)
1178         m_node->document ()->post (m_node, new Posting (m_node, MsgMediaUpdated));
1179 }
1180 
1181 KDE_NO_EXPORT void ImageMedia::movieResize (const QSize &) {
1182     //kDebug () << "movieResize" << endl;
1183     if (m_node)
1184         m_node->document ()->post (m_node, new Posting (m_node, MsgMediaUpdated));
1185 }
1186 
1187 KDE_NO_EXPORT void ImageMedia::movieUpdated (const QRect &) {
1188     if (frame_nr++) {
1189         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 KDE_NO_EXPORT 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.toAscii ());
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 "mediaobject.moc"