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"